Arbeiten aus dem URlaub
This commit is contained in:
1
blight-game/.gitignore
vendored
Normal file
1
blight-game/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/.gradle/
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 810 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 810 KiB |
@@ -1,81 +1,53 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
}
|
||||
|
||||
group = 'de.blight'
|
||||
version = '0.1.0'
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
resources {
|
||||
srcDirs = ['src/main/resources', 'assets']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass = 'de.blight.game.BlightApp'
|
||||
// jMonkeyEngine benötigt den headless-Flag nicht; nativer Fenstermodus
|
||||
applicationDefaultJvmArgs = [
|
||||
'--add-opens', 'java.base/java.lang=ALL-UNNAMED',
|
||||
'--add-opens', 'java.desktop/sun.awt=ALL-UNNAMED',
|
||||
'-Djava.library.path=${rootDir}/build/natives'
|
||||
]
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
ext {
|
||||
jmeVersion = '3.7.0-stable'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// jMonkeyEngine core
|
||||
implementation "org.jmonkeyengine:jme3-core:${jmeVersion}"
|
||||
implementation "org.jmonkeyengine:jme3-desktop:${jmeVersion}"
|
||||
implementation "org.jmonkeyengine:jme3-lwjgl3:${jmeVersion}"
|
||||
|
||||
// Terrain, Effects, Plugins
|
||||
implementation "org.jmonkeyengine:jme3-terrain:${jmeVersion}"
|
||||
implementation "org.jmonkeyengine:jme3-effects:${jmeVersion}"
|
||||
|
||||
// Bullet-Physik (nativ)
|
||||
implementation "org.jmonkeyengine:jme3-jbullet:${jmeVersion}"
|
||||
|
||||
// Testdaten / eingebaute Assets (Primitiv-Modelle, Skybox etc.)
|
||||
implementation "org.jmonkeyengine:jme3-testdata:${jmeVersion}"
|
||||
|
||||
// JSON für Key-Binding-Konfiguration
|
||||
implementation 'com.google.code.gson:gson:2.11.0'
|
||||
}
|
||||
|
||||
// Native Libraries automatisch entpacken
|
||||
tasks.register('extractNatives', Copy) {
|
||||
def nativeConf = configurations.runtimeClasspath.resolvedConfiguration
|
||||
.resolvedArtifacts
|
||||
.findAll { it.name.contains('natives') }
|
||||
.collect { zipTree(it.file) }
|
||||
|
||||
from nativeConf
|
||||
into "${buildDir}/natives"
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
}
|
||||
|
||||
run {
|
||||
dependsOn extractNatives
|
||||
workingDir = rootDir
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes 'Main-Class': application.mainClass
|
||||
}
|
||||
}
|
||||
// group / version / java / repositories kommen vom Root-Build.
|
||||
plugins {
|
||||
id 'application'
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass = 'de.blight.game.BlightApp'
|
||||
applicationDefaultJvmArgs = [
|
||||
'--add-opens', 'java.base/java.lang=ALL-UNNAMED',
|
||||
'--add-opens', 'java.desktop/sun.awt=ALL-UNNAMED',
|
||||
"-Djava.library.path=${buildDir}/natives"
|
||||
]
|
||||
}
|
||||
|
||||
ext {
|
||||
jmeVersion = '3.7.0-stable'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':blight-common')
|
||||
implementation project(':blight-assets')
|
||||
|
||||
implementation "org.jmonkeyengine:jme3-core:${jmeVersion}"
|
||||
implementation "org.jmonkeyengine:jme3-desktop:${jmeVersion}"
|
||||
implementation "org.jmonkeyengine:jme3-lwjgl3:${jmeVersion}"
|
||||
implementation "org.jmonkeyengine:jme3-terrain:${jmeVersion}"
|
||||
implementation "org.jmonkeyengine:jme3-effects:${jmeVersion}"
|
||||
implementation "org.jmonkeyengine:jme3-jbullet:${jmeVersion}"
|
||||
implementation "org.jmonkeyengine:jme3-testdata:${jmeVersion}"
|
||||
implementation 'com.google.code.gson:gson:2.11.0'
|
||||
}
|
||||
|
||||
tasks.register('extractNatives', Copy) {
|
||||
def nativeConf = configurations.runtimeClasspath.resolvedConfiguration
|
||||
.resolvedArtifacts
|
||||
.findAll { it.name.contains('natives') }
|
||||
.collect { zipTree(it.file) }
|
||||
|
||||
from nativeConf
|
||||
into "${buildDir}/natives"
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
}
|
||||
|
||||
run {
|
||||
dependsOn extractNatives
|
||||
workingDir = rootDir // gemeinsames Arbeitsverzeichnis = Projekt-Root
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes 'Main-Class': application.mainClass
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
498
blight-game/gradlew
vendored
498
blight-game/gradlew
vendored
@@ -1,249 +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='-Dfile.encoding=UTF-8 "-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" "$@"
|
||||
#!/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='-Dfile.encoding=UTF-8 "-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" "$@"
|
||||
|
||||
184
blight-game/gradlew.bat
vendored
184
blight-game/gradlew.bat
vendored
@@ -1,92 +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=-Dfile.encoding=UTF-8 "-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
|
||||
@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=-Dfile.encoding=UTF-8 "-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
|
||||
|
||||
@@ -1 +1,8 @@
|
||||
rootProject.name = 'blight-game'
|
||||
rootProject.name = 'blight-game'
|
||||
|
||||
// Sibling-Projekte einbinden.
|
||||
include 'blight-common'
|
||||
project(':blight-common').projectDir = new File(settingsDir, '../blight-common')
|
||||
|
||||
include 'blight-assets'
|
||||
project(':blight-assets').projectDir = new File(settingsDir, '../blight-assets')
|
||||
|
||||
@@ -1,92 +1,92 @@
|
||||
package de.blight.game;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.input.KeyInput;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.KeyTrigger;
|
||||
import com.jme3.system.AppSettings;
|
||||
import de.blight.game.config.*;
|
||||
import de.blight.game.scene.WorldScene;
|
||||
|
||||
public class BlightApp extends SimpleApplication {
|
||||
|
||||
private KeyBindings keyBindings;
|
||||
private GraphicsSettings graphicsSettings;
|
||||
private WorldScene worldScene;
|
||||
private ConfigScreen configScreen;
|
||||
private GraphicsScreen graphicsScreen;
|
||||
private PauseMenu pauseMenu;
|
||||
|
||||
public static void main(String[] args) {
|
||||
BlightApp app = new BlightApp();
|
||||
|
||||
GraphicsSettings gs = GraphicsStore.load();
|
||||
AppSettings settings = new AppSettings(true);
|
||||
settings.setTitle("Blight");
|
||||
settings.setResolution(gs.width, gs.height);
|
||||
settings.setFullscreen(gs.fullscreen);
|
||||
settings.setVSync(gs.vsync);
|
||||
settings.setSamples(gs.samples);
|
||||
|
||||
app.setSettings(settings);
|
||||
app.setShowSettings(false);
|
||||
app.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simpleInitApp() {
|
||||
flyCam.setEnabled(false);
|
||||
inputManager.deleteMapping(INPUT_MAPPING_EXIT);
|
||||
|
||||
keyBindings = KeyBindingStore.load();
|
||||
graphicsSettings = GraphicsStore.load();
|
||||
|
||||
worldScene = new WorldScene(keyBindings);
|
||||
stateManager.attach(worldScene);
|
||||
|
||||
configScreen = new ConfigScreen(keyBindings, () -> worldScene.reloadBindings(keyBindings));
|
||||
configScreen.setOnClose(() -> pauseMenu.setEnabled(true));
|
||||
stateManager.attach(configScreen);
|
||||
configScreen.setEnabled(false);
|
||||
|
||||
graphicsScreen = new GraphicsScreen(graphicsSettings, () -> pauseMenu.setEnabled(true));
|
||||
stateManager.attach(graphicsScreen);
|
||||
graphicsScreen.setEnabled(false);
|
||||
|
||||
pauseMenu = new PauseMenu(
|
||||
() -> { pauseMenu.setEnabled(false); graphicsScreen.setEnabled(true); },
|
||||
() -> { pauseMenu.setEnabled(false); configScreen.setEnabled(true); }
|
||||
);
|
||||
stateManager.attach(pauseMenu);
|
||||
pauseMenu.setEnabled(false);
|
||||
|
||||
inputManager.addMapping("ToggleMenu", new KeyTrigger(KeyInput.KEY_ESCAPE));
|
||||
inputManager.addListener((ActionListener) (name, isPressed, tpf) -> {
|
||||
if (!isPressed) return;
|
||||
|
||||
if (graphicsScreen.isEnabled()) {
|
||||
// GraphicsScreen wird nur über seine eigenen Buttons geschlossen
|
||||
return;
|
||||
}
|
||||
if (configScreen.isEnabled()) {
|
||||
if (configScreen.isWaiting()) {
|
||||
configScreen.cancelWaiting();
|
||||
} else {
|
||||
configScreen.setEnabled(false);
|
||||
pauseMenu.setEnabled(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (pauseMenu.isEnabled()) {
|
||||
pauseMenu.setEnabled(false);
|
||||
worldScene.setPaused(false);
|
||||
return;
|
||||
}
|
||||
pauseMenu.setEnabled(true);
|
||||
worldScene.setPaused(true);
|
||||
}, "ToggleMenu");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simpleUpdate(float tpf) {}
|
||||
}
|
||||
package de.blight.game;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.input.KeyInput;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.KeyTrigger;
|
||||
import com.jme3.system.AppSettings;
|
||||
import de.blight.game.config.*;
|
||||
import de.blight.game.scene.WorldScene;
|
||||
|
||||
public class BlightApp extends SimpleApplication {
|
||||
|
||||
private KeyBindings keyBindings;
|
||||
private GraphicsSettings graphicsSettings;
|
||||
private WorldScene worldScene;
|
||||
private ConfigScreen configScreen;
|
||||
private GraphicsScreen graphicsScreen;
|
||||
private PauseMenu pauseMenu;
|
||||
|
||||
public static void main(String[] args) {
|
||||
BlightApp app = new BlightApp();
|
||||
|
||||
GraphicsSettings gs = GraphicsStore.load();
|
||||
AppSettings settings = new AppSettings(true);
|
||||
settings.setTitle("Blight");
|
||||
settings.setResolution(gs.width, gs.height);
|
||||
settings.setFullscreen(gs.fullscreen);
|
||||
settings.setVSync(gs.vsync);
|
||||
settings.setSamples(gs.samples);
|
||||
|
||||
app.setSettings(settings);
|
||||
app.setShowSettings(false);
|
||||
app.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simpleInitApp() {
|
||||
flyCam.setEnabled(false);
|
||||
inputManager.deleteMapping(INPUT_MAPPING_EXIT);
|
||||
|
||||
keyBindings = KeyBindingStore.load();
|
||||
graphicsSettings = GraphicsStore.load();
|
||||
|
||||
worldScene = new WorldScene(keyBindings);
|
||||
stateManager.attach(worldScene);
|
||||
|
||||
configScreen = new ConfigScreen(keyBindings, () -> worldScene.reloadBindings(keyBindings));
|
||||
configScreen.setOnClose(() -> pauseMenu.setEnabled(true));
|
||||
stateManager.attach(configScreen);
|
||||
configScreen.setEnabled(false);
|
||||
|
||||
graphicsScreen = new GraphicsScreen(graphicsSettings, () -> pauseMenu.setEnabled(true));
|
||||
stateManager.attach(graphicsScreen);
|
||||
graphicsScreen.setEnabled(false);
|
||||
|
||||
pauseMenu = new PauseMenu(
|
||||
() -> { pauseMenu.setEnabled(false); graphicsScreen.setEnabled(true); },
|
||||
() -> { pauseMenu.setEnabled(false); configScreen.setEnabled(true); }
|
||||
);
|
||||
stateManager.attach(pauseMenu);
|
||||
pauseMenu.setEnabled(false);
|
||||
|
||||
inputManager.addMapping("ToggleMenu", new KeyTrigger(KeyInput.KEY_ESCAPE));
|
||||
inputManager.addListener((ActionListener) (name, isPressed, tpf) -> {
|
||||
if (!isPressed) return;
|
||||
|
||||
if (graphicsScreen.isEnabled()) {
|
||||
// GraphicsScreen wird nur über seine eigenen Buttons geschlossen
|
||||
return;
|
||||
}
|
||||
if (configScreen.isEnabled()) {
|
||||
if (configScreen.isWaiting()) {
|
||||
configScreen.cancelWaiting();
|
||||
} else {
|
||||
configScreen.setEnabled(false);
|
||||
pauseMenu.setEnabled(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (pauseMenu.isEnabled()) {
|
||||
pauseMenu.setEnabled(false);
|
||||
worldScene.setPaused(false);
|
||||
return;
|
||||
}
|
||||
pauseMenu.setEnabled(true);
|
||||
worldScene.setPaused(true);
|
||||
}, "ToggleMenu");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simpleUpdate(float tpf) {}
|
||||
}
|
||||
|
||||
@@ -1,309 +1,309 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.font.BitmapFont;
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.input.KeyInput;
|
||||
import com.jme3.input.MouseInput;
|
||||
import com.jme3.input.RawInputListener;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.MouseButtonTrigger;
|
||||
import com.jme3.input.event.JoyAxisEvent;
|
||||
import com.jme3.input.event.JoyButtonEvent;
|
||||
import com.jme3.input.event.KeyInputEvent;
|
||||
import com.jme3.input.event.MouseButtonEvent;
|
||||
import com.jme3.input.event.MouseMotionEvent;
|
||||
import com.jme3.input.event.TouchEvent;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
|
||||
/**
|
||||
* Overlay-AppState der die Tastenbelegungs-Maske anzeigt.
|
||||
*
|
||||
* ESC → Schließen (ohne Speichern)
|
||||
* Klick auf Row → wartet auf neue Taste
|
||||
* ESC während Warten → bricht nur die Zuweisung ab
|
||||
* Speichern → schreibt JSON, ruft onSave-Callback
|
||||
*/
|
||||
public class ConfigScreen extends BaseAppState implements RawInputListener {
|
||||
|
||||
// Farben
|
||||
private static final ColorRGBA COL_BG = new ColorRGBA(0.05f, 0.05f, 0.08f, 0.88f);
|
||||
private static final ColorRGBA COL_PANEL = new ColorRGBA(0.10f, 0.10f, 0.16f, 1.00f);
|
||||
private static final ColorRGBA COL_ROW = new ColorRGBA(0.18f, 0.18f, 0.28f, 1.00f);
|
||||
private static final ColorRGBA COL_ROW_HOVER = new ColorRGBA(0.25f, 0.25f, 0.40f, 1.00f);
|
||||
private static final ColorRGBA COL_ROW_WAIT = new ColorRGBA(0.50f, 0.30f, 0.10f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN_SAVE = new ColorRGBA(0.15f, 0.40f, 0.15f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN_CANCEL = new ColorRGBA(0.40f, 0.15f, 0.15f, 1.00f);
|
||||
private static final ColorRGBA COL_TEXT = ColorRGBA.White;
|
||||
private static final ColorRGBA COL_TEXT_KEY = new ColorRGBA(0.85f, 0.85f, 0.50f, 1.00f);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private SimpleApplication app;
|
||||
private Node guiNode;
|
||||
private BitmapFont font;
|
||||
|
||||
private KeyBindings liveBindings; // geteilt mit der ganzen App
|
||||
private KeyBindings editCopy; // wird beim Öffnen geklont
|
||||
|
||||
private Runnable onSave; // Callback → PlayerInputControl.reloadBindings
|
||||
private Runnable onClose; // Callback → PauseMenu wiederherstellen
|
||||
|
||||
private Node panel;
|
||||
private List<Row> rows = new ArrayList<>();
|
||||
private int waitingRow = -1; // -1 = keine Zuweisung aktiv
|
||||
|
||||
// UI-Elemente für Buttons (Bounds in Screen-Koordinaten)
|
||||
private float saveBtnX, saveBtnY, saveBtnW, saveBtnH;
|
||||
private float cancelBtnX, cancelBtnY;
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private static class Row {
|
||||
String field;
|
||||
String label;
|
||||
BitmapText keyText;
|
||||
Geometry bg;
|
||||
float x, y, w, h; // Button-Bounds
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
public ConfigScreen(KeyBindings liveBindings, Runnable onSave) {
|
||||
this.liveBindings = liveBindings;
|
||||
this.onSave = onSave;
|
||||
}
|
||||
|
||||
public boolean isWaiting() { return waitingRow >= 0; }
|
||||
|
||||
public void setOnClose(Runnable onClose) { this.onClose = onClose; }
|
||||
|
||||
public void cancelWaiting() {
|
||||
if (waitingRow >= 0) { resetRowColor(waitingRow); waitingRow = -1; }
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Lifecycle
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
this.app = (SimpleApplication) app;
|
||||
this.guiNode = this.app.getGuiNode();
|
||||
this.font = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnable() {
|
||||
editCopy = liveBindings.copy();
|
||||
waitingRow = -1;
|
||||
buildUI();
|
||||
app.getInputManager().setCursorVisible(true);
|
||||
app.getInputManager().addRawInputListener(this);
|
||||
app.getInputManager().addMapping("_CfgClick", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
|
||||
app.getInputManager().addListener(clickListener, "_CfgClick");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDisable() {
|
||||
if (panel != null) { guiNode.detachChild(panel); panel = null; }
|
||||
rows.clear();
|
||||
waitingRow = -1;
|
||||
app.getInputManager().removeRawInputListener(this);
|
||||
app.getInputManager().deleteMapping("_CfgClick");
|
||||
app.getInputManager().setCursorVisible(false);
|
||||
}
|
||||
|
||||
@Override protected void cleanup(Application app) {}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// UI aufbauen
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private void buildUI() {
|
||||
float sw = app.getCamera().getWidth();
|
||||
float sh = app.getCamera().getHeight();
|
||||
|
||||
panel = new Node("cfg-panel");
|
||||
|
||||
// Halbdurchsichtiger Overlay über dem Spiel
|
||||
addQuad(panel, 0, 0, sw, sh, COL_BG, -2);
|
||||
|
||||
float pw = 720, ph = 440;
|
||||
float px = (sw - pw) / 2f, py = (sh - ph) / 2f;
|
||||
addQuad(panel, px, py, pw, ph, COL_PANEL, -1);
|
||||
|
||||
// Titel
|
||||
BitmapText title = text("TASTENBELEGUNG", 20, COL_TEXT);
|
||||
centerText(title, px, py + ph - 40, pw);
|
||||
panel.attachChild(title);
|
||||
|
||||
BitmapText hint = text("Klicke eine Taste um sie neu zu belegen", 14, new ColorRGBA(0.7f, 0.7f, 0.7f, 1f));
|
||||
centerText(hint, px, py + ph - 70, pw);
|
||||
panel.attachChild(hint);
|
||||
|
||||
// Reihen
|
||||
float rowX = px + 30;
|
||||
float keyX = px + pw - 220;
|
||||
float rowW = 180;
|
||||
float rowH = 36;
|
||||
float startY = py + ph - 110;
|
||||
float stepY = 48;
|
||||
|
||||
for (int i = 0; i < KeyBindings.ENTRIES.length; i++) {
|
||||
String[] entry = KeyBindings.ENTRIES[i];
|
||||
float ry = startY - i * stepY;
|
||||
|
||||
BitmapText lbl = text(entry[1], 16, COL_TEXT);
|
||||
lbl.setLocalTranslation(rowX, ry + rowH - 8, 0);
|
||||
panel.attachChild(lbl);
|
||||
|
||||
Geometry bg = addQuad(panel, keyX, ry, rowW, rowH, COL_ROW, 0);
|
||||
|
||||
BitmapText kt = text(KeyNames.of(editCopy.get(entry[0])), 16, COL_TEXT_KEY);
|
||||
kt.setLocalTranslation(keyX + 10, ry + rowH - 8, 1);
|
||||
panel.attachChild(kt);
|
||||
|
||||
Row row = new Row();
|
||||
row.field = entry[0];
|
||||
row.label = entry[1];
|
||||
row.keyText = kt;
|
||||
row.bg = bg;
|
||||
row.x = keyX; row.y = ry; row.w = rowW; row.h = rowH;
|
||||
rows.add(row);
|
||||
}
|
||||
|
||||
// Buttons
|
||||
float btnW = 160, btnH = 42;
|
||||
float btnY = py + 25;
|
||||
saveBtnX = px + pw / 2f - btnW - 15;
|
||||
saveBtnY = btnY;
|
||||
saveBtnW = btnW;
|
||||
saveBtnH = btnH;
|
||||
cancelBtnX = px + pw / 2f + 15;
|
||||
cancelBtnY = btnY;
|
||||
|
||||
addQuad(panel, saveBtnX, saveBtnY, btnW, btnH, COL_BTN_SAVE, 0);
|
||||
BitmapText saveLabel = text("Speichern", 16, COL_TEXT);
|
||||
centerText(saveLabel, saveBtnX, saveBtnY + btnH - 10, btnW);
|
||||
panel.attachChild(saveLabel);
|
||||
|
||||
addQuad(panel, cancelBtnX, cancelBtnY, btnW, btnH, COL_BTN_CANCEL, 0);
|
||||
BitmapText cancelLabel = text("Abbrechen", 16, COL_TEXT);
|
||||
centerText(cancelLabel, cancelBtnX, cancelBtnY + btnH - 10, btnW);
|
||||
panel.attachChild(cancelLabel);
|
||||
|
||||
guiNode.attachChild(panel);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Mausklick
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private final ActionListener clickListener = (name, isPressed, tpf) -> {
|
||||
if (!isPressed) return;
|
||||
Vector2f cursor = app.getInputManager().getCursorPosition();
|
||||
|
||||
// Reihen prüfen
|
||||
for (int i = 0; i < rows.size(); i++) {
|
||||
Row r = rows.get(i);
|
||||
if (hits(cursor, r.x, r.y, r.w, r.h)) {
|
||||
waitingRow = i;
|
||||
r.bg.getMaterial().setColor("Color", COL_ROW_WAIT);
|
||||
r.keyText.setText("...");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Speichern
|
||||
if (hits(cursor, saveBtnX, saveBtnY, saveBtnW, saveBtnH)) {
|
||||
liveBindings.copyFrom(editCopy);
|
||||
KeyBindingStore.save(liveBindings);
|
||||
if (onSave != null) onSave.run();
|
||||
setEnabled(false);
|
||||
if (onClose != null) onClose.run();
|
||||
return;
|
||||
}
|
||||
|
||||
// Abbrechen
|
||||
if (hits(cursor, cancelBtnX, cancelBtnY, saveBtnW, saveBtnH)) {
|
||||
setEnabled(false);
|
||||
if (onClose != null) onClose.run();
|
||||
}
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Tastendruck beim Warten auf Zuweisung (RawInputListener)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onKeyEvent(KeyInputEvent evt) {
|
||||
if (!evt.isPressed() || waitingRow < 0) return;
|
||||
if (evt.getKeyCode() == KeyInput.KEY_ESCAPE) return; // cancelWaiting() wird von BlightApp aufgerufen
|
||||
|
||||
Row r = rows.get(waitingRow);
|
||||
editCopy.set(r.field, evt.getKeyCode());
|
||||
r.keyText.setText(KeyNames.of(evt.getKeyCode()));
|
||||
resetRowColor(waitingRow);
|
||||
waitingRow = -1;
|
||||
}
|
||||
|
||||
private void resetRowColor(int idx) {
|
||||
rows.get(idx).bg.getMaterial().setColor("Color", COL_ROW);
|
||||
}
|
||||
|
||||
// RawInputListener-Pflichtmethoden
|
||||
@Override public void beginInput() {}
|
||||
@Override public void endInput() {}
|
||||
@Override public void onMouseMotionEvent(MouseMotionEvent evt) {}
|
||||
@Override public void onMouseButtonEvent(MouseButtonEvent evt) {}
|
||||
@Override public void onJoyAxisEvent(JoyAxisEvent evt) {}
|
||||
@Override public void onJoyButtonEvent(JoyButtonEvent evt) {}
|
||||
@Override public void onTouchEvent(TouchEvent evt) {}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Hilfsmethoden
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private Geometry addQuad(Node parent, float x, float y, float w, float h, ColorRGBA color, float z) {
|
||||
Geometry geo = new Geometry("q", new Quad(w, h));
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", color.clone());
|
||||
if (color.a < 1f) {
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
geo.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
}
|
||||
geo.setMaterial(mat);
|
||||
geo.setLocalTranslation(x, y, z);
|
||||
parent.attachChild(geo);
|
||||
return geo;
|
||||
}
|
||||
|
||||
private BitmapText text(String content, int size, ColorRGBA color) {
|
||||
BitmapText t = new BitmapText(font, false);
|
||||
t.setSize(size);
|
||||
t.setColor(color);
|
||||
t.setText(content);
|
||||
return t;
|
||||
}
|
||||
|
||||
private void centerText(BitmapText t, float x, float y, float width) {
|
||||
t.setLocalTranslation(x + (width - t.getLineWidth()) / 2f, y, 1);
|
||||
}
|
||||
|
||||
private boolean hits(Vector2f p, float x, float y, float w, float h) {
|
||||
return p.x >= x && p.x <= x + w && p.y >= y && p.y <= y + h;
|
||||
}
|
||||
}
|
||||
package de.blight.game.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.font.BitmapFont;
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.input.KeyInput;
|
||||
import com.jme3.input.MouseInput;
|
||||
import com.jme3.input.RawInputListener;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.MouseButtonTrigger;
|
||||
import com.jme3.input.event.JoyAxisEvent;
|
||||
import com.jme3.input.event.JoyButtonEvent;
|
||||
import com.jme3.input.event.KeyInputEvent;
|
||||
import com.jme3.input.event.MouseButtonEvent;
|
||||
import com.jme3.input.event.MouseMotionEvent;
|
||||
import com.jme3.input.event.TouchEvent;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
|
||||
/**
|
||||
* Overlay-AppState der die Tastenbelegungs-Maske anzeigt.
|
||||
*
|
||||
* ESC → Schließen (ohne Speichern)
|
||||
* Klick auf Row → wartet auf neue Taste
|
||||
* ESC während Warten → bricht nur die Zuweisung ab
|
||||
* Speichern → schreibt JSON, ruft onSave-Callback
|
||||
*/
|
||||
public class ConfigScreen extends BaseAppState implements RawInputListener {
|
||||
|
||||
// Farben
|
||||
private static final ColorRGBA COL_BG = new ColorRGBA(0.05f, 0.05f, 0.08f, 0.88f);
|
||||
private static final ColorRGBA COL_PANEL = new ColorRGBA(0.10f, 0.10f, 0.16f, 1.00f);
|
||||
private static final ColorRGBA COL_ROW = new ColorRGBA(0.18f, 0.18f, 0.28f, 1.00f);
|
||||
private static final ColorRGBA COL_ROW_HOVER = new ColorRGBA(0.25f, 0.25f, 0.40f, 1.00f);
|
||||
private static final ColorRGBA COL_ROW_WAIT = new ColorRGBA(0.50f, 0.30f, 0.10f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN_SAVE = new ColorRGBA(0.15f, 0.40f, 0.15f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN_CANCEL = new ColorRGBA(0.40f, 0.15f, 0.15f, 1.00f);
|
||||
private static final ColorRGBA COL_TEXT = ColorRGBA.White;
|
||||
private static final ColorRGBA COL_TEXT_KEY = new ColorRGBA(0.85f, 0.85f, 0.50f, 1.00f);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private SimpleApplication app;
|
||||
private Node guiNode;
|
||||
private BitmapFont font;
|
||||
|
||||
private KeyBindings liveBindings; // geteilt mit der ganzen App
|
||||
private KeyBindings editCopy; // wird beim Öffnen geklont
|
||||
|
||||
private Runnable onSave; // Callback → PlayerInputControl.reloadBindings
|
||||
private Runnable onClose; // Callback → PauseMenu wiederherstellen
|
||||
|
||||
private Node panel;
|
||||
private List<Row> rows = new ArrayList<>();
|
||||
private int waitingRow = -1; // -1 = keine Zuweisung aktiv
|
||||
|
||||
// UI-Elemente für Buttons (Bounds in Screen-Koordinaten)
|
||||
private float saveBtnX, saveBtnY, saveBtnW, saveBtnH;
|
||||
private float cancelBtnX, cancelBtnY;
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private static class Row {
|
||||
String field;
|
||||
String label;
|
||||
BitmapText keyText;
|
||||
Geometry bg;
|
||||
float x, y, w, h; // Button-Bounds
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
public ConfigScreen(KeyBindings liveBindings, Runnable onSave) {
|
||||
this.liveBindings = liveBindings;
|
||||
this.onSave = onSave;
|
||||
}
|
||||
|
||||
public boolean isWaiting() { return waitingRow >= 0; }
|
||||
|
||||
public void setOnClose(Runnable onClose) { this.onClose = onClose; }
|
||||
|
||||
public void cancelWaiting() {
|
||||
if (waitingRow >= 0) { resetRowColor(waitingRow); waitingRow = -1; }
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Lifecycle
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
this.app = (SimpleApplication) app;
|
||||
this.guiNode = this.app.getGuiNode();
|
||||
this.font = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnable() {
|
||||
editCopy = liveBindings.copy();
|
||||
waitingRow = -1;
|
||||
buildUI();
|
||||
app.getInputManager().setCursorVisible(true);
|
||||
app.getInputManager().addRawInputListener(this);
|
||||
app.getInputManager().addMapping("_CfgClick", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
|
||||
app.getInputManager().addListener(clickListener, "_CfgClick");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDisable() {
|
||||
if (panel != null) { guiNode.detachChild(panel); panel = null; }
|
||||
rows.clear();
|
||||
waitingRow = -1;
|
||||
app.getInputManager().removeRawInputListener(this);
|
||||
app.getInputManager().deleteMapping("_CfgClick");
|
||||
app.getInputManager().setCursorVisible(false);
|
||||
}
|
||||
|
||||
@Override protected void cleanup(Application app) {}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// UI aufbauen
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private void buildUI() {
|
||||
float sw = app.getCamera().getWidth();
|
||||
float sh = app.getCamera().getHeight();
|
||||
|
||||
panel = new Node("cfg-panel");
|
||||
|
||||
// Halbdurchsichtiger Overlay über dem Spiel
|
||||
addQuad(panel, 0, 0, sw, sh, COL_BG, -2);
|
||||
|
||||
float pw = 720, ph = 440;
|
||||
float px = (sw - pw) / 2f, py = (sh - ph) / 2f;
|
||||
addQuad(panel, px, py, pw, ph, COL_PANEL, -1);
|
||||
|
||||
// Titel
|
||||
BitmapText title = text("TASTENBELEGUNG", 20, COL_TEXT);
|
||||
centerText(title, px, py + ph - 40, pw);
|
||||
panel.attachChild(title);
|
||||
|
||||
BitmapText hint = text("Klicke eine Taste um sie neu zu belegen", 14, new ColorRGBA(0.7f, 0.7f, 0.7f, 1f));
|
||||
centerText(hint, px, py + ph - 70, pw);
|
||||
panel.attachChild(hint);
|
||||
|
||||
// Reihen
|
||||
float rowX = px + 30;
|
||||
float keyX = px + pw - 220;
|
||||
float rowW = 180;
|
||||
float rowH = 36;
|
||||
float startY = py + ph - 110;
|
||||
float stepY = 48;
|
||||
|
||||
for (int i = 0; i < KeyBindings.ENTRIES.length; i++) {
|
||||
String[] entry = KeyBindings.ENTRIES[i];
|
||||
float ry = startY - i * stepY;
|
||||
|
||||
BitmapText lbl = text(entry[1], 16, COL_TEXT);
|
||||
lbl.setLocalTranslation(rowX, ry + rowH - 8, 0);
|
||||
panel.attachChild(lbl);
|
||||
|
||||
Geometry bg = addQuad(panel, keyX, ry, rowW, rowH, COL_ROW, 0);
|
||||
|
||||
BitmapText kt = text(KeyNames.of(editCopy.get(entry[0])), 16, COL_TEXT_KEY);
|
||||
kt.setLocalTranslation(keyX + 10, ry + rowH - 8, 1);
|
||||
panel.attachChild(kt);
|
||||
|
||||
Row row = new Row();
|
||||
row.field = entry[0];
|
||||
row.label = entry[1];
|
||||
row.keyText = kt;
|
||||
row.bg = bg;
|
||||
row.x = keyX; row.y = ry; row.w = rowW; row.h = rowH;
|
||||
rows.add(row);
|
||||
}
|
||||
|
||||
// Buttons
|
||||
float btnW = 160, btnH = 42;
|
||||
float btnY = py + 25;
|
||||
saveBtnX = px + pw / 2f - btnW - 15;
|
||||
saveBtnY = btnY;
|
||||
saveBtnW = btnW;
|
||||
saveBtnH = btnH;
|
||||
cancelBtnX = px + pw / 2f + 15;
|
||||
cancelBtnY = btnY;
|
||||
|
||||
addQuad(panel, saveBtnX, saveBtnY, btnW, btnH, COL_BTN_SAVE, 0);
|
||||
BitmapText saveLabel = text("Speichern", 16, COL_TEXT);
|
||||
centerText(saveLabel, saveBtnX, saveBtnY + btnH - 10, btnW);
|
||||
panel.attachChild(saveLabel);
|
||||
|
||||
addQuad(panel, cancelBtnX, cancelBtnY, btnW, btnH, COL_BTN_CANCEL, 0);
|
||||
BitmapText cancelLabel = text("Abbrechen", 16, COL_TEXT);
|
||||
centerText(cancelLabel, cancelBtnX, cancelBtnY + btnH - 10, btnW);
|
||||
panel.attachChild(cancelLabel);
|
||||
|
||||
guiNode.attachChild(panel);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Mausklick
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private final ActionListener clickListener = (name, isPressed, tpf) -> {
|
||||
if (!isPressed) return;
|
||||
Vector2f cursor = app.getInputManager().getCursorPosition();
|
||||
|
||||
// Reihen prüfen
|
||||
for (int i = 0; i < rows.size(); i++) {
|
||||
Row r = rows.get(i);
|
||||
if (hits(cursor, r.x, r.y, r.w, r.h)) {
|
||||
waitingRow = i;
|
||||
r.bg.getMaterial().setColor("Color", COL_ROW_WAIT);
|
||||
r.keyText.setText("...");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Speichern
|
||||
if (hits(cursor, saveBtnX, saveBtnY, saveBtnW, saveBtnH)) {
|
||||
liveBindings.copyFrom(editCopy);
|
||||
KeyBindingStore.save(liveBindings);
|
||||
if (onSave != null) onSave.run();
|
||||
setEnabled(false);
|
||||
if (onClose != null) onClose.run();
|
||||
return;
|
||||
}
|
||||
|
||||
// Abbrechen
|
||||
if (hits(cursor, cancelBtnX, cancelBtnY, saveBtnW, saveBtnH)) {
|
||||
setEnabled(false);
|
||||
if (onClose != null) onClose.run();
|
||||
}
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Tastendruck beim Warten auf Zuweisung (RawInputListener)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onKeyEvent(KeyInputEvent evt) {
|
||||
if (!evt.isPressed() || waitingRow < 0) return;
|
||||
if (evt.getKeyCode() == KeyInput.KEY_ESCAPE) return; // cancelWaiting() wird von BlightApp aufgerufen
|
||||
|
||||
Row r = rows.get(waitingRow);
|
||||
editCopy.set(r.field, evt.getKeyCode());
|
||||
r.keyText.setText(KeyNames.of(evt.getKeyCode()));
|
||||
resetRowColor(waitingRow);
|
||||
waitingRow = -1;
|
||||
}
|
||||
|
||||
private void resetRowColor(int idx) {
|
||||
rows.get(idx).bg.getMaterial().setColor("Color", COL_ROW);
|
||||
}
|
||||
|
||||
// RawInputListener-Pflichtmethoden
|
||||
@Override public void beginInput() {}
|
||||
@Override public void endInput() {}
|
||||
@Override public void onMouseMotionEvent(MouseMotionEvent evt) {}
|
||||
@Override public void onMouseButtonEvent(MouseButtonEvent evt) {}
|
||||
@Override public void onJoyAxisEvent(JoyAxisEvent evt) {}
|
||||
@Override public void onJoyButtonEvent(JoyButtonEvent evt) {}
|
||||
@Override public void onTouchEvent(TouchEvent evt) {}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Hilfsmethoden
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private Geometry addQuad(Node parent, float x, float y, float w, float h, ColorRGBA color, float z) {
|
||||
Geometry geo = new Geometry("q", new Quad(w, h));
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", color.clone());
|
||||
if (color.a < 1f) {
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
geo.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
}
|
||||
geo.setMaterial(mat);
|
||||
geo.setLocalTranslation(x, y, z);
|
||||
parent.attachChild(geo);
|
||||
return geo;
|
||||
}
|
||||
|
||||
private BitmapText text(String content, int size, ColorRGBA color) {
|
||||
BitmapText t = new BitmapText(font, false);
|
||||
t.setSize(size);
|
||||
t.setColor(color);
|
||||
t.setText(content);
|
||||
return t;
|
||||
}
|
||||
|
||||
private void centerText(BitmapText t, float x, float y, float width) {
|
||||
t.setLocalTranslation(x + (width - t.getLineWidth()) / 2f, y, 1);
|
||||
}
|
||||
|
||||
private boolean hits(Vector2f p, float x, float y, float w, float h) {
|
||||
return p.x >= x && p.x <= x + w && p.y >= y && p.y <= y + h;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,290 +1,290 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.font.BitmapFont;
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.input.MouseInput;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.MouseButtonTrigger;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
import com.jme3.system.AppSettings;
|
||||
|
||||
public class GraphicsScreen extends BaseAppState {
|
||||
|
||||
private static final ColorRGBA COL_BG = new ColorRGBA(0.05f, 0.05f, 0.08f, 0.88f);
|
||||
private static final ColorRGBA COL_PANEL = new ColorRGBA(0.10f, 0.10f, 0.16f, 1.00f);
|
||||
private static final ColorRGBA COL_ROW = new ColorRGBA(0.18f, 0.18f, 0.28f, 1.00f);
|
||||
private static final ColorRGBA COL_ARROW = new ColorRGBA(0.28f, 0.28f, 0.44f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN_OK = new ColorRGBA(0.15f, 0.40f, 0.15f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN_CANCEL = new ColorRGBA(0.40f, 0.15f, 0.15f, 1.00f);
|
||||
private static final ColorRGBA COL_TEXT = ColorRGBA.White;
|
||||
private static final ColorRGBA COL_TEXT_VAL = new ColorRGBA(0.85f, 0.85f, 0.50f, 1.00f);
|
||||
|
||||
private static final int[][] RESOLUTIONS = {
|
||||
{1280, 720}, {1600, 900}, {1920, 1080}, {2560, 1440}, {3840, 2160}
|
||||
};
|
||||
private static final int[] SAMPLES = {0, 2, 4, 8};
|
||||
|
||||
private static final int ROW_RES = 0;
|
||||
private static final int ROW_FULL = 1;
|
||||
private static final int ROW_VSYNC = 2;
|
||||
private static final int ROW_AA = 3;
|
||||
|
||||
private SimpleApplication app;
|
||||
private Node guiNode;
|
||||
private BitmapFont font;
|
||||
private Node panel;
|
||||
|
||||
private final GraphicsSettings live;
|
||||
private GraphicsSettings edit;
|
||||
private final Runnable onClose;
|
||||
|
||||
private int resIdx;
|
||||
private int samplesIdx;
|
||||
|
||||
// Per-row layout (indexed by ROW_*)
|
||||
private final float[] cellX = new float[4];
|
||||
private final float[] cellY = new float[4];
|
||||
private final float[] cellW = new float[4];
|
||||
private final float cellH = 36;
|
||||
private final float arrW = 30;
|
||||
private final float[] leftX = new float[4];
|
||||
private final float[] rightX = new float[4];
|
||||
private final BitmapText[] valTexts = new BitmapText[4];
|
||||
|
||||
private float okX, okY, okW, okH;
|
||||
private float cancelX, cancelY;
|
||||
|
||||
public GraphicsScreen(GraphicsSettings live, Runnable onClose) {
|
||||
this.live = live;
|
||||
this.onClose = onClose;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
this.app = (SimpleApplication) app;
|
||||
this.guiNode = this.app.getGuiNode();
|
||||
this.font = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnable() {
|
||||
edit = new GraphicsSettings();
|
||||
edit.width = live.width; edit.height = live.height;
|
||||
edit.fullscreen = live.fullscreen;
|
||||
edit.vsync = live.vsync;
|
||||
edit.samples = live.samples;
|
||||
|
||||
resIdx = 0;
|
||||
for (int i = 0; i < RESOLUTIONS.length; i++) {
|
||||
if (RESOLUTIONS[i][0] == edit.width && RESOLUTIONS[i][1] == edit.height) {
|
||||
resIdx = i; break;
|
||||
}
|
||||
}
|
||||
samplesIdx = 0;
|
||||
for (int i = 0; i < SAMPLES.length; i++) {
|
||||
if (SAMPLES[i] == edit.samples) { samplesIdx = i; break; }
|
||||
}
|
||||
|
||||
buildUI();
|
||||
app.getInputManager().setCursorVisible(true);
|
||||
app.getInputManager().addMapping("_GfxClick", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
|
||||
app.getInputManager().addListener(clickListener, "_GfxClick");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDisable() {
|
||||
if (panel != null) { guiNode.detachChild(panel); panel = null; }
|
||||
app.getInputManager().deleteMapping("_GfxClick");
|
||||
app.getInputManager().setCursorVisible(false);
|
||||
}
|
||||
|
||||
@Override protected void cleanup(Application app) {}
|
||||
|
||||
private void buildUI() {
|
||||
float sw = app.getCamera().getWidth();
|
||||
float sh = app.getCamera().getHeight();
|
||||
panel = new Node("gfx-panel");
|
||||
addQuad(panel, 0, 0, sw, sh, COL_BG, -2);
|
||||
|
||||
float pw = 640, ph = 400;
|
||||
float px = (sw - pw) / 2f, py = (sh - ph) / 2f;
|
||||
addQuad(panel, px, py, pw, ph, COL_PANEL, -1);
|
||||
|
||||
BitmapText title = txt("GRAFIKEINSTELLUNGEN", 20, COL_TEXT);
|
||||
centerText(title, px, py + ph - 42, pw);
|
||||
panel.attachChild(title);
|
||||
|
||||
String[] labels = {"Auflösung", "Vollbild", "VSync", "Kantenglättung"};
|
||||
float lblX = px + 30;
|
||||
float vx = px + pw - 270;
|
||||
float vw = 190;
|
||||
float startY = py + ph - 100;
|
||||
float step = 60;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float ry = startY - i * step;
|
||||
|
||||
BitmapText lbl = txt(labels[i], 16, COL_TEXT);
|
||||
lbl.setLocalTranslation(lblX, ry + cellH - 8, 0);
|
||||
panel.attachChild(lbl);
|
||||
|
||||
// Left arrow
|
||||
addQuad(panel, vx - arrW - 6, ry, arrW, cellH, COL_ARROW, 0);
|
||||
BitmapText lt = txt("<", 16, COL_TEXT);
|
||||
lt.setLocalTranslation(vx - arrW - 6 + (arrW - lt.getLineWidth()) / 2f, ry + cellH - 8, 1);
|
||||
panel.attachChild(lt);
|
||||
|
||||
// Value cell
|
||||
addQuad(panel, vx, ry, vw, cellH, COL_ROW, 0);
|
||||
|
||||
// Right arrow
|
||||
addQuad(panel, vx + vw + 6, ry, arrW, cellH, COL_ARROW, 0);
|
||||
BitmapText rt = txt(">", 16, COL_TEXT);
|
||||
rt.setLocalTranslation(vx + vw + 6 + (arrW - rt.getLineWidth()) / 2f, ry + cellH - 8, 1);
|
||||
panel.attachChild(rt);
|
||||
|
||||
BitmapText vt = txt("", 16, COL_TEXT_VAL);
|
||||
panel.attachChild(vt);
|
||||
valTexts[i] = vt;
|
||||
|
||||
cellX[i] = vx; cellY[i] = ry; cellW[i] = vw;
|
||||
leftX[i] = vx - arrW - 6;
|
||||
rightX[i] = vx + vw + 6;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) refreshText(i);
|
||||
|
||||
float bw = 160, bh = 42;
|
||||
okW = bw; okH = bh;
|
||||
okX = px + pw / 2f - bw - 10;
|
||||
okY = py + 22;
|
||||
cancelX = px + pw / 2f + 10;
|
||||
cancelY = py + 22;
|
||||
|
||||
addQuad(panel, okX, okY, bw, bh, COL_BTN_OK, 0);
|
||||
BitmapText okLbl = txt("Übernehmen", 16, COL_TEXT);
|
||||
centerText(okLbl, okX, okY + bh - 10, bw);
|
||||
panel.attachChild(okLbl);
|
||||
|
||||
addQuad(panel, cancelX, cancelY, bw, bh, COL_BTN_CANCEL, 0);
|
||||
BitmapText cancelLbl = txt("Abbrechen", 16, COL_TEXT);
|
||||
centerText(cancelLbl, cancelX, cancelY + bh - 10, bw);
|
||||
panel.attachChild(cancelLbl);
|
||||
|
||||
guiNode.attachChild(panel);
|
||||
}
|
||||
|
||||
private void refreshText(int row) {
|
||||
String val = switch (row) {
|
||||
case ROW_RES -> RESOLUTIONS[resIdx][0] + "x" + RESOLUTIONS[resIdx][1];
|
||||
case ROW_FULL -> edit.fullscreen ? "An" : "Aus";
|
||||
case ROW_VSYNC -> edit.vsync ? "An" : "Aus";
|
||||
case ROW_AA -> SAMPLES[samplesIdx] == 0 ? "Aus" : SAMPLES[samplesIdx] + "x MSAA";
|
||||
default -> "";
|
||||
};
|
||||
BitmapText vt = valTexts[row];
|
||||
vt.setText(val);
|
||||
vt.setLocalTranslation(
|
||||
cellX[row] + (cellW[row] - vt.getLineWidth()) / 2f,
|
||||
cellY[row] + cellH - 8,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
private final ActionListener clickListener = (name, isPressed, tpf) -> {
|
||||
if (!isPressed) return;
|
||||
Vector2f c = app.getInputManager().getCursorPosition();
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (hits(c, leftX[i], cellY[i], arrW, cellH)) { cycleRow(i, -1); return; }
|
||||
if (hits(c, rightX[i], cellY[i], arrW, cellH)) { cycleRow(i, +1); return; }
|
||||
}
|
||||
if (hits(c, okX, okY, okW, okH)) { applyAndSave(); return; }
|
||||
if (hits(c, cancelX, cancelY, okW, okH)) { close(); }
|
||||
};
|
||||
|
||||
private void cycleRow(int row, int dir) {
|
||||
switch (row) {
|
||||
case ROW_RES:
|
||||
resIdx = (resIdx + dir + RESOLUTIONS.length) % RESOLUTIONS.length;
|
||||
edit.width = RESOLUTIONS[resIdx][0];
|
||||
edit.height = RESOLUTIONS[resIdx][1];
|
||||
break;
|
||||
case ROW_FULL:
|
||||
edit.fullscreen = !edit.fullscreen;
|
||||
break;
|
||||
case ROW_VSYNC:
|
||||
edit.vsync = !edit.vsync;
|
||||
break;
|
||||
case ROW_AA:
|
||||
samplesIdx = (samplesIdx + dir + SAMPLES.length) % SAMPLES.length;
|
||||
edit.samples = SAMPLES[samplesIdx];
|
||||
break;
|
||||
}
|
||||
refreshText(row);
|
||||
}
|
||||
|
||||
private void applyAndSave() {
|
||||
live.width = edit.width; live.height = edit.height;
|
||||
live.fullscreen = edit.fullscreen;
|
||||
live.vsync = edit.vsync;
|
||||
live.samples = edit.samples;
|
||||
|
||||
GraphicsStore.save(live);
|
||||
|
||||
AppSettings s = app.getContext().getSettings();
|
||||
s.setResolution(live.width, live.height);
|
||||
s.setFullscreen(live.fullscreen);
|
||||
s.setVSync(live.vsync);
|
||||
s.setSamples(live.samples);
|
||||
app.setSettings(s);
|
||||
|
||||
close();
|
||||
app.restart();
|
||||
}
|
||||
|
||||
private void close() {
|
||||
setEnabled(false);
|
||||
if (onClose != null) onClose.run();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private Geometry addQuad(Node parent, float x, float y, float w, float h, ColorRGBA color, float z) {
|
||||
Geometry geo = new Geometry("q", new Quad(w, h));
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", color.clone());
|
||||
if (color.a < 1f) {
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
geo.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
}
|
||||
geo.setMaterial(mat);
|
||||
geo.setLocalTranslation(x, y, z);
|
||||
parent.attachChild(geo);
|
||||
return geo;
|
||||
}
|
||||
|
||||
private BitmapText txt(String s, int size, ColorRGBA color) {
|
||||
BitmapText t = new BitmapText(font, false);
|
||||
t.setSize(size); t.setColor(color); t.setText(s);
|
||||
return t;
|
||||
}
|
||||
|
||||
private void centerText(BitmapText t, float x, float y, float width) {
|
||||
t.setLocalTranslation(x + (width - t.getLineWidth()) / 2f, y, 1);
|
||||
}
|
||||
|
||||
private boolean hits(Vector2f p, float x, float y, float w, float h) {
|
||||
return p.x >= x && p.x <= x + w && p.y >= y && p.y <= y + h;
|
||||
}
|
||||
}
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.font.BitmapFont;
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.input.MouseInput;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.MouseButtonTrigger;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
import com.jme3.system.AppSettings;
|
||||
|
||||
public class GraphicsScreen extends BaseAppState {
|
||||
|
||||
private static final ColorRGBA COL_BG = new ColorRGBA(0.05f, 0.05f, 0.08f, 0.88f);
|
||||
private static final ColorRGBA COL_PANEL = new ColorRGBA(0.10f, 0.10f, 0.16f, 1.00f);
|
||||
private static final ColorRGBA COL_ROW = new ColorRGBA(0.18f, 0.18f, 0.28f, 1.00f);
|
||||
private static final ColorRGBA COL_ARROW = new ColorRGBA(0.28f, 0.28f, 0.44f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN_OK = new ColorRGBA(0.15f, 0.40f, 0.15f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN_CANCEL = new ColorRGBA(0.40f, 0.15f, 0.15f, 1.00f);
|
||||
private static final ColorRGBA COL_TEXT = ColorRGBA.White;
|
||||
private static final ColorRGBA COL_TEXT_VAL = new ColorRGBA(0.85f, 0.85f, 0.50f, 1.00f);
|
||||
|
||||
private static final int[][] RESOLUTIONS = {
|
||||
{1280, 720}, {1600, 900}, {1920, 1080}, {2560, 1440}, {3840, 2160}
|
||||
};
|
||||
private static final int[] SAMPLES = {0, 2, 4, 8};
|
||||
|
||||
private static final int ROW_RES = 0;
|
||||
private static final int ROW_FULL = 1;
|
||||
private static final int ROW_VSYNC = 2;
|
||||
private static final int ROW_AA = 3;
|
||||
|
||||
private SimpleApplication app;
|
||||
private Node guiNode;
|
||||
private BitmapFont font;
|
||||
private Node panel;
|
||||
|
||||
private final GraphicsSettings live;
|
||||
private GraphicsSettings edit;
|
||||
private final Runnable onClose;
|
||||
|
||||
private int resIdx;
|
||||
private int samplesIdx;
|
||||
|
||||
// Per-row layout (indexed by ROW_*)
|
||||
private final float[] cellX = new float[4];
|
||||
private final float[] cellY = new float[4];
|
||||
private final float[] cellW = new float[4];
|
||||
private final float cellH = 36;
|
||||
private final float arrW = 30;
|
||||
private final float[] leftX = new float[4];
|
||||
private final float[] rightX = new float[4];
|
||||
private final BitmapText[] valTexts = new BitmapText[4];
|
||||
|
||||
private float okX, okY, okW, okH;
|
||||
private float cancelX, cancelY;
|
||||
|
||||
public GraphicsScreen(GraphicsSettings live, Runnable onClose) {
|
||||
this.live = live;
|
||||
this.onClose = onClose;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
this.app = (SimpleApplication) app;
|
||||
this.guiNode = this.app.getGuiNode();
|
||||
this.font = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnable() {
|
||||
edit = new GraphicsSettings();
|
||||
edit.width = live.width; edit.height = live.height;
|
||||
edit.fullscreen = live.fullscreen;
|
||||
edit.vsync = live.vsync;
|
||||
edit.samples = live.samples;
|
||||
|
||||
resIdx = 0;
|
||||
for (int i = 0; i < RESOLUTIONS.length; i++) {
|
||||
if (RESOLUTIONS[i][0] == edit.width && RESOLUTIONS[i][1] == edit.height) {
|
||||
resIdx = i; break;
|
||||
}
|
||||
}
|
||||
samplesIdx = 0;
|
||||
for (int i = 0; i < SAMPLES.length; i++) {
|
||||
if (SAMPLES[i] == edit.samples) { samplesIdx = i; break; }
|
||||
}
|
||||
|
||||
buildUI();
|
||||
app.getInputManager().setCursorVisible(true);
|
||||
app.getInputManager().addMapping("_GfxClick", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
|
||||
app.getInputManager().addListener(clickListener, "_GfxClick");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDisable() {
|
||||
if (panel != null) { guiNode.detachChild(panel); panel = null; }
|
||||
app.getInputManager().deleteMapping("_GfxClick");
|
||||
app.getInputManager().setCursorVisible(false);
|
||||
}
|
||||
|
||||
@Override protected void cleanup(Application app) {}
|
||||
|
||||
private void buildUI() {
|
||||
float sw = app.getCamera().getWidth();
|
||||
float sh = app.getCamera().getHeight();
|
||||
panel = new Node("gfx-panel");
|
||||
addQuad(panel, 0, 0, sw, sh, COL_BG, -2);
|
||||
|
||||
float pw = 640, ph = 400;
|
||||
float px = (sw - pw) / 2f, py = (sh - ph) / 2f;
|
||||
addQuad(panel, px, py, pw, ph, COL_PANEL, -1);
|
||||
|
||||
BitmapText title = txt("GRAFIKEINSTELLUNGEN", 20, COL_TEXT);
|
||||
centerText(title, px, py + ph - 42, pw);
|
||||
panel.attachChild(title);
|
||||
|
||||
String[] labels = {"Auflösung", "Vollbild", "VSync", "Kantenglättung"};
|
||||
float lblX = px + 30;
|
||||
float vx = px + pw - 270;
|
||||
float vw = 190;
|
||||
float startY = py + ph - 100;
|
||||
float step = 60;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float ry = startY - i * step;
|
||||
|
||||
BitmapText lbl = txt(labels[i], 16, COL_TEXT);
|
||||
lbl.setLocalTranslation(lblX, ry + cellH - 8, 0);
|
||||
panel.attachChild(lbl);
|
||||
|
||||
// Left arrow
|
||||
addQuad(panel, vx - arrW - 6, ry, arrW, cellH, COL_ARROW, 0);
|
||||
BitmapText lt = txt("<", 16, COL_TEXT);
|
||||
lt.setLocalTranslation(vx - arrW - 6 + (arrW - lt.getLineWidth()) / 2f, ry + cellH - 8, 1);
|
||||
panel.attachChild(lt);
|
||||
|
||||
// Value cell
|
||||
addQuad(panel, vx, ry, vw, cellH, COL_ROW, 0);
|
||||
|
||||
// Right arrow
|
||||
addQuad(panel, vx + vw + 6, ry, arrW, cellH, COL_ARROW, 0);
|
||||
BitmapText rt = txt(">", 16, COL_TEXT);
|
||||
rt.setLocalTranslation(vx + vw + 6 + (arrW - rt.getLineWidth()) / 2f, ry + cellH - 8, 1);
|
||||
panel.attachChild(rt);
|
||||
|
||||
BitmapText vt = txt("", 16, COL_TEXT_VAL);
|
||||
panel.attachChild(vt);
|
||||
valTexts[i] = vt;
|
||||
|
||||
cellX[i] = vx; cellY[i] = ry; cellW[i] = vw;
|
||||
leftX[i] = vx - arrW - 6;
|
||||
rightX[i] = vx + vw + 6;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) refreshText(i);
|
||||
|
||||
float bw = 160, bh = 42;
|
||||
okW = bw; okH = bh;
|
||||
okX = px + pw / 2f - bw - 10;
|
||||
okY = py + 22;
|
||||
cancelX = px + pw / 2f + 10;
|
||||
cancelY = py + 22;
|
||||
|
||||
addQuad(panel, okX, okY, bw, bh, COL_BTN_OK, 0);
|
||||
BitmapText okLbl = txt("Übernehmen", 16, COL_TEXT);
|
||||
centerText(okLbl, okX, okY + bh - 10, bw);
|
||||
panel.attachChild(okLbl);
|
||||
|
||||
addQuad(panel, cancelX, cancelY, bw, bh, COL_BTN_CANCEL, 0);
|
||||
BitmapText cancelLbl = txt("Abbrechen", 16, COL_TEXT);
|
||||
centerText(cancelLbl, cancelX, cancelY + bh - 10, bw);
|
||||
panel.attachChild(cancelLbl);
|
||||
|
||||
guiNode.attachChild(panel);
|
||||
}
|
||||
|
||||
private void refreshText(int row) {
|
||||
String val = switch (row) {
|
||||
case ROW_RES -> RESOLUTIONS[resIdx][0] + "x" + RESOLUTIONS[resIdx][1];
|
||||
case ROW_FULL -> edit.fullscreen ? "An" : "Aus";
|
||||
case ROW_VSYNC -> edit.vsync ? "An" : "Aus";
|
||||
case ROW_AA -> SAMPLES[samplesIdx] == 0 ? "Aus" : SAMPLES[samplesIdx] + "x MSAA";
|
||||
default -> "";
|
||||
};
|
||||
BitmapText vt = valTexts[row];
|
||||
vt.setText(val);
|
||||
vt.setLocalTranslation(
|
||||
cellX[row] + (cellW[row] - vt.getLineWidth()) / 2f,
|
||||
cellY[row] + cellH - 8,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
private final ActionListener clickListener = (name, isPressed, tpf) -> {
|
||||
if (!isPressed) return;
|
||||
Vector2f c = app.getInputManager().getCursorPosition();
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (hits(c, leftX[i], cellY[i], arrW, cellH)) { cycleRow(i, -1); return; }
|
||||
if (hits(c, rightX[i], cellY[i], arrW, cellH)) { cycleRow(i, +1); return; }
|
||||
}
|
||||
if (hits(c, okX, okY, okW, okH)) { applyAndSave(); return; }
|
||||
if (hits(c, cancelX, cancelY, okW, okH)) { close(); }
|
||||
};
|
||||
|
||||
private void cycleRow(int row, int dir) {
|
||||
switch (row) {
|
||||
case ROW_RES:
|
||||
resIdx = (resIdx + dir + RESOLUTIONS.length) % RESOLUTIONS.length;
|
||||
edit.width = RESOLUTIONS[resIdx][0];
|
||||
edit.height = RESOLUTIONS[resIdx][1];
|
||||
break;
|
||||
case ROW_FULL:
|
||||
edit.fullscreen = !edit.fullscreen;
|
||||
break;
|
||||
case ROW_VSYNC:
|
||||
edit.vsync = !edit.vsync;
|
||||
break;
|
||||
case ROW_AA:
|
||||
samplesIdx = (samplesIdx + dir + SAMPLES.length) % SAMPLES.length;
|
||||
edit.samples = SAMPLES[samplesIdx];
|
||||
break;
|
||||
}
|
||||
refreshText(row);
|
||||
}
|
||||
|
||||
private void applyAndSave() {
|
||||
live.width = edit.width; live.height = edit.height;
|
||||
live.fullscreen = edit.fullscreen;
|
||||
live.vsync = edit.vsync;
|
||||
live.samples = edit.samples;
|
||||
|
||||
GraphicsStore.save(live);
|
||||
|
||||
AppSettings s = app.getContext().getSettings();
|
||||
s.setResolution(live.width, live.height);
|
||||
s.setFullscreen(live.fullscreen);
|
||||
s.setVSync(live.vsync);
|
||||
s.setSamples(live.samples);
|
||||
app.setSettings(s);
|
||||
|
||||
close();
|
||||
app.restart();
|
||||
}
|
||||
|
||||
private void close() {
|
||||
setEnabled(false);
|
||||
if (onClose != null) onClose.run();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private Geometry addQuad(Node parent, float x, float y, float w, float h, ColorRGBA color, float z) {
|
||||
Geometry geo = new Geometry("q", new Quad(w, h));
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", color.clone());
|
||||
if (color.a < 1f) {
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
geo.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
}
|
||||
geo.setMaterial(mat);
|
||||
geo.setLocalTranslation(x, y, z);
|
||||
parent.attachChild(geo);
|
||||
return geo;
|
||||
}
|
||||
|
||||
private BitmapText txt(String s, int size, ColorRGBA color) {
|
||||
BitmapText t = new BitmapText(font, false);
|
||||
t.setSize(size); t.setColor(color); t.setText(s);
|
||||
return t;
|
||||
}
|
||||
|
||||
private void centerText(BitmapText t, float x, float y, float width) {
|
||||
t.setLocalTranslation(x + (width - t.getLineWidth()) / 2f, y, 1);
|
||||
}
|
||||
|
||||
private boolean hits(Vector2f p, float x, float y, float w, float h) {
|
||||
return p.x >= x && p.x <= x + w && p.y >= y && p.y <= y + h;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
public class GraphicsSettings {
|
||||
public int width = 1280;
|
||||
public int height = 720;
|
||||
public boolean fullscreen = false;
|
||||
public boolean vsync = false;
|
||||
public int samples = 4;
|
||||
}
|
||||
package de.blight.game.config;
|
||||
|
||||
public class GraphicsSettings {
|
||||
public int width = 1280;
|
||||
public int height = 720;
|
||||
public boolean fullscreen = false;
|
||||
public boolean vsync = false;
|
||||
public int samples = 4;
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
|
||||
public class GraphicsStore {
|
||||
|
||||
private static final Path FILE = Paths.get("config", "graphics.json");
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
public static GraphicsSettings load() {
|
||||
if (Files.exists(FILE)) {
|
||||
try (Reader r = Files.newBufferedReader(FILE)) {
|
||||
return GSON.fromJson(r, GraphicsSettings.class);
|
||||
} catch (IOException e) {
|
||||
System.err.println("graphics.json konnte nicht geladen werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
return new GraphicsSettings();
|
||||
}
|
||||
|
||||
public static void save(GraphicsSettings gs) {
|
||||
try {
|
||||
Files.createDirectories(FILE.getParent());
|
||||
try (Writer w = Files.newBufferedWriter(FILE)) {
|
||||
GSON.toJson(gs, w);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("graphics.json konnte nicht gespeichert werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
|
||||
public class GraphicsStore {
|
||||
|
||||
private static final Path FILE = Paths.get("config", "graphics.json");
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
public static GraphicsSettings load() {
|
||||
if (Files.exists(FILE)) {
|
||||
try (Reader r = Files.newBufferedReader(FILE)) {
|
||||
return GSON.fromJson(r, GraphicsSettings.class);
|
||||
} catch (IOException e) {
|
||||
System.err.println("graphics.json konnte nicht geladen werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
return new GraphicsSettings();
|
||||
}
|
||||
|
||||
public static void save(GraphicsSettings gs) {
|
||||
try {
|
||||
Files.createDirectories(FILE.getParent());
|
||||
try (Writer w = Files.newBufferedWriter(FILE)) {
|
||||
GSON.toJson(gs, w);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("graphics.json konnte nicht gespeichert werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
|
||||
public class KeyBindingStore {
|
||||
|
||||
private static final Path FILE = Paths.get("config", "keybindings.json");
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
public static KeyBindings load() {
|
||||
if (Files.exists(FILE)) {
|
||||
try (Reader r = Files.newBufferedReader(FILE)) {
|
||||
return GSON.fromJson(r, KeyBindings.class);
|
||||
} catch (IOException e) {
|
||||
System.err.println("keybindings.json konnte nicht geladen werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
return new KeyBindings();
|
||||
}
|
||||
|
||||
public static void save(KeyBindings kb) {
|
||||
try {
|
||||
Files.createDirectories(FILE.getParent());
|
||||
try (Writer w = Files.newBufferedWriter(FILE)) {
|
||||
GSON.toJson(kb, w);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("keybindings.json konnte nicht gespeichert werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
|
||||
public class KeyBindingStore {
|
||||
|
||||
private static final Path FILE = Paths.get("config", "keybindings.json");
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
public static KeyBindings load() {
|
||||
if (Files.exists(FILE)) {
|
||||
try (Reader r = Files.newBufferedReader(FILE)) {
|
||||
return GSON.fromJson(r, KeyBindings.class);
|
||||
} catch (IOException e) {
|
||||
System.err.println("keybindings.json konnte nicht geladen werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
return new KeyBindings();
|
||||
}
|
||||
|
||||
public static void save(KeyBindings kb) {
|
||||
try {
|
||||
Files.createDirectories(FILE.getParent());
|
||||
try (Writer w = Files.newBufferedWriter(FILE)) {
|
||||
GSON.toJson(kb, w);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("keybindings.json konnte nicht gespeichert werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.jme3.input.KeyInput;
|
||||
|
||||
/** Speichert alle konfigurierbaren Tastenbelegungen als plain int-Felder (KeyInput-Codes). */
|
||||
public class KeyBindings {
|
||||
|
||||
public int forward = KeyInput.KEY_W;
|
||||
public int backward = KeyInput.KEY_S;
|
||||
public int left = KeyInput.KEY_A;
|
||||
public int right = KeyInput.KEY_D;
|
||||
public int jump = KeyInput.KEY_SPACE;
|
||||
public int sprint = KeyInput.KEY_LSHIFT;
|
||||
|
||||
/** Metadaten für die Config-UI: Feldname im Objekt + Anzeigename. */
|
||||
public static final String[][] ENTRIES = {
|
||||
{"forward", "Vorwärts"},
|
||||
{"backward", "Rückwärts"},
|
||||
{"left", "Links"},
|
||||
{"right", "Rechts"},
|
||||
{"jump", "Springen"},
|
||||
{"sprint", "Rennen"},
|
||||
};
|
||||
|
||||
public int get(String fieldName) {
|
||||
try { return (int) KeyBindings.class.getField(fieldName).get(this); }
|
||||
catch (Exception e) { return 0; }
|
||||
}
|
||||
|
||||
public void set(String fieldName, int keyCode) {
|
||||
try { KeyBindings.class.getField(fieldName).setInt(this, keyCode); }
|
||||
catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
public KeyBindings copy() {
|
||||
KeyBindings c = new KeyBindings();
|
||||
for (String[] e : ENTRIES) c.set(e[0], get(e[0]));
|
||||
return c;
|
||||
}
|
||||
|
||||
public void copyFrom(KeyBindings src) {
|
||||
for (String[] e : ENTRIES) set(e[0], src.get(e[0]));
|
||||
}
|
||||
}
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.jme3.input.KeyInput;
|
||||
|
||||
/** Speichert alle konfigurierbaren Tastenbelegungen als plain int-Felder (KeyInput-Codes). */
|
||||
public class KeyBindings {
|
||||
|
||||
public int forward = KeyInput.KEY_W;
|
||||
public int backward = KeyInput.KEY_S;
|
||||
public int left = KeyInput.KEY_A;
|
||||
public int right = KeyInput.KEY_D;
|
||||
public int jump = KeyInput.KEY_SPACE;
|
||||
public int sprint = KeyInput.KEY_LSHIFT;
|
||||
|
||||
/** Metadaten für die Config-UI: Feldname im Objekt + Anzeigename. */
|
||||
public static final String[][] ENTRIES = {
|
||||
{"forward", "Vorwärts"},
|
||||
{"backward", "Rückwärts"},
|
||||
{"left", "Links"},
|
||||
{"right", "Rechts"},
|
||||
{"jump", "Springen"},
|
||||
{"sprint", "Rennen"},
|
||||
};
|
||||
|
||||
public int get(String fieldName) {
|
||||
try { return (int) KeyBindings.class.getField(fieldName).get(this); }
|
||||
catch (Exception e) { return 0; }
|
||||
}
|
||||
|
||||
public void set(String fieldName, int keyCode) {
|
||||
try { KeyBindings.class.getField(fieldName).setInt(this, keyCode); }
|
||||
catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
public KeyBindings copy() {
|
||||
KeyBindings c = new KeyBindings();
|
||||
for (String[] e : ENTRIES) c.set(e[0], get(e[0]));
|
||||
return c;
|
||||
}
|
||||
|
||||
public void copyFrom(KeyBindings src) {
|
||||
for (String[] e : ENTRIES) set(e[0], src.get(e[0]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.jme3.input.KeyInput;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/** Bildet KeyInput-Codes auf lesbare Namen ab (via Reflection auf KeyInput-Konstanten). */
|
||||
public final class KeyNames {
|
||||
|
||||
private static final Map<Integer, String> NAMES = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (Field f : KeyInput.class.getFields()) {
|
||||
if (f.getName().startsWith("KEY_") && f.getType() == int.class) {
|
||||
try {
|
||||
NAMES.put(f.getInt(null), pretty(f.getName().substring(4)));
|
||||
} catch (IllegalAccessException ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String of(int keyCode) {
|
||||
return NAMES.getOrDefault(keyCode, "Key#" + keyCode);
|
||||
}
|
||||
|
||||
private static String pretty(String raw) {
|
||||
// "LSHIFT" → "L-Shift", "SPACE" → "Space", "W" → "W"
|
||||
return switch (raw) {
|
||||
case "SPACE" -> "Space";
|
||||
case "LSHIFT" -> "L-Shift";
|
||||
case "RSHIFT" -> "R-Shift";
|
||||
case "LCONTROL"-> "L-Ctrl";
|
||||
case "RCONTROL"-> "R-Ctrl";
|
||||
case "LMENU" -> "L-Alt";
|
||||
case "RMENU" -> "R-Alt";
|
||||
case "RETURN" -> "Enter";
|
||||
case "BACK" -> "Backspace";
|
||||
case "UP" -> "Pfeil-Hoch";
|
||||
case "DOWN" -> "Pfeil-Runter";
|
||||
case "LEFT" -> "Pfeil-Links";
|
||||
case "RIGHT" -> "Pfeil-Rechts";
|
||||
default -> raw.length() == 1 ? raw : capitalize(raw);
|
||||
};
|
||||
}
|
||||
|
||||
private static String capitalize(String s) {
|
||||
return s.isEmpty() ? s : s.charAt(0) + s.substring(1).toLowerCase();
|
||||
}
|
||||
}
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.jme3.input.KeyInput;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/** Bildet KeyInput-Codes auf lesbare Namen ab (via Reflection auf KeyInput-Konstanten). */
|
||||
public final class KeyNames {
|
||||
|
||||
private static final Map<Integer, String> NAMES = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (Field f : KeyInput.class.getFields()) {
|
||||
if (f.getName().startsWith("KEY_") && f.getType() == int.class) {
|
||||
try {
|
||||
NAMES.put(f.getInt(null), pretty(f.getName().substring(4)));
|
||||
} catch (IllegalAccessException ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String of(int keyCode) {
|
||||
return NAMES.getOrDefault(keyCode, "Key#" + keyCode);
|
||||
}
|
||||
|
||||
private static String pretty(String raw) {
|
||||
// "LSHIFT" → "L-Shift", "SPACE" → "Space", "W" → "W"
|
||||
return switch (raw) {
|
||||
case "SPACE" -> "Space";
|
||||
case "LSHIFT" -> "L-Shift";
|
||||
case "RSHIFT" -> "R-Shift";
|
||||
case "LCONTROL"-> "L-Ctrl";
|
||||
case "RCONTROL"-> "R-Ctrl";
|
||||
case "LMENU" -> "L-Alt";
|
||||
case "RMENU" -> "R-Alt";
|
||||
case "RETURN" -> "Enter";
|
||||
case "BACK" -> "Backspace";
|
||||
case "UP" -> "Pfeil-Hoch";
|
||||
case "DOWN" -> "Pfeil-Runter";
|
||||
case "LEFT" -> "Pfeil-Links";
|
||||
case "RIGHT" -> "Pfeil-Rechts";
|
||||
default -> raw.length() == 1 ? raw : capitalize(raw);
|
||||
};
|
||||
}
|
||||
|
||||
private static String capitalize(String s) {
|
||||
return s.isEmpty() ? s : s.charAt(0) + s.substring(1).toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,168 +1,168 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.font.BitmapFont;
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.input.MouseInput;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.MouseButtonTrigger;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
|
||||
public class PauseMenu extends BaseAppState {
|
||||
|
||||
private static final ColorRGBA COL_BG = new ColorRGBA(0.05f, 0.05f, 0.08f, 0.88f);
|
||||
private static final ColorRGBA COL_PANEL = new ColorRGBA(0.10f, 0.10f, 0.16f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN = new ColorRGBA(0.18f, 0.18f, 0.28f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN_DIS = new ColorRGBA(0.12f, 0.12f, 0.18f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN_QUIT = new ColorRGBA(0.38f, 0.10f, 0.10f, 1.00f);
|
||||
private static final ColorRGBA COL_TEXT = ColorRGBA.White;
|
||||
private static final ColorRGBA COL_TEXT_DIS = new ColorRGBA(0.40f, 0.40f, 0.40f, 1.00f);
|
||||
private static final ColorRGBA COL_TEXT_SUB = new ColorRGBA(0.35f, 0.35f, 0.35f, 1.00f);
|
||||
|
||||
private static final int BTN_GRAFIK = 0;
|
||||
private static final int BTN_AUDIO = 1;
|
||||
private static final int BTN_STEUERUNG = 2;
|
||||
private static final int BTN_BEENDEN = 3;
|
||||
|
||||
private SimpleApplication app;
|
||||
private Node guiNode;
|
||||
private BitmapFont font;
|
||||
private Node panel;
|
||||
|
||||
private Runnable onGraphics;
|
||||
private Runnable onControls;
|
||||
|
||||
// [x, y, w, h] per button
|
||||
private final float[][] btnBounds = new float[4][4];
|
||||
|
||||
public PauseMenu(Runnable onGraphics, Runnable onControls) {
|
||||
this.onGraphics = onGraphics;
|
||||
this.onControls = onControls;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
this.app = (SimpleApplication) app;
|
||||
this.guiNode = this.app.getGuiNode();
|
||||
this.font = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnable() {
|
||||
buildUI();
|
||||
app.getInputManager().setCursorVisible(true);
|
||||
app.getInputManager().addMapping("_PauseClick", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
|
||||
app.getInputManager().addListener(clickListener, "_PauseClick");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDisable() {
|
||||
if (panel != null) { guiNode.detachChild(panel); panel = null; }
|
||||
app.getInputManager().deleteMapping("_PauseClick");
|
||||
app.getInputManager().setCursorVisible(false);
|
||||
}
|
||||
|
||||
@Override protected void cleanup(Application app) {}
|
||||
|
||||
private void buildUI() {
|
||||
float sw = app.getCamera().getWidth();
|
||||
float sh = app.getCamera().getHeight();
|
||||
panel = new Node("pause-panel");
|
||||
addQuad(panel, 0, 0, sw, sh, COL_BG, -2);
|
||||
|
||||
float pw = 320, ph = 360;
|
||||
float px = (sw - pw) / 2f, py = (sh - ph) / 2f;
|
||||
addQuad(panel, px, py, pw, ph, COL_PANEL, -1);
|
||||
|
||||
BitmapText title = txt("PAUSE", 26, COL_TEXT);
|
||||
centerText(title, px, py + ph - 48, pw);
|
||||
panel.attachChild(title);
|
||||
|
||||
String[] labels = {"Grafik", "Audio", "Steuerung", "Beenden"};
|
||||
boolean[] enabled = {true, false, true, true};
|
||||
float bw = 260, bh = 52;
|
||||
float bx = px + (pw - bw) / 2f;
|
||||
float startY = py + ph - 112;
|
||||
float step = 62;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float by = startY - i * step;
|
||||
ColorRGBA bgCol = !enabled[i] ? COL_BTN_DIS : (i == BTN_BEENDEN ? COL_BTN_QUIT : COL_BTN);
|
||||
ColorRGBA txCol = enabled[i] ? COL_TEXT : COL_TEXT_DIS;
|
||||
|
||||
addQuad(panel, bx, by, bw, bh, bgCol, 0);
|
||||
|
||||
BitmapText lbl = txt(labels[i], 18, txCol);
|
||||
if (!enabled[i]) {
|
||||
// Center label in upper portion, show hint below
|
||||
lbl.setLocalTranslation(bx + (bw - lbl.getLineWidth()) / 2f, by + bh - 12, 1);
|
||||
BitmapText hint = txt("Bald verfügbar", 12, COL_TEXT_SUB);
|
||||
hint.setLocalTranslation(bx + (bw - hint.getLineWidth()) / 2f, by + 14, 1);
|
||||
panel.attachChild(hint);
|
||||
} else {
|
||||
centerText(lbl, bx, by + bh - 16, bw);
|
||||
}
|
||||
panel.attachChild(lbl);
|
||||
|
||||
btnBounds[i][0] = bx; btnBounds[i][1] = by;
|
||||
btnBounds[i][2] = bw; btnBounds[i][3] = bh;
|
||||
}
|
||||
|
||||
guiNode.attachChild(panel);
|
||||
}
|
||||
|
||||
private final ActionListener clickListener = (name, isPressed, tpf) -> {
|
||||
if (!isPressed) return;
|
||||
Vector2f c = app.getInputManager().getCursorPosition();
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (!hits(c, btnBounds[i][0], btnBounds[i][1], btnBounds[i][2], btnBounds[i][3])) continue;
|
||||
switch (i) {
|
||||
case BTN_GRAFIK -> { if (onGraphics != null) onGraphics.run(); }
|
||||
case BTN_AUDIO -> { /* Bald verfügbar */ }
|
||||
case BTN_STEUERUNG -> { if (onControls != null) onControls.run(); }
|
||||
case BTN_BEENDEN -> app.stop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private Geometry addQuad(Node parent, float x, float y, float w, float h, ColorRGBA color, float z) {
|
||||
Geometry geo = new Geometry("q", new Quad(w, h));
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", color.clone());
|
||||
if (color.a < 1f) {
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
geo.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
}
|
||||
geo.setMaterial(mat);
|
||||
geo.setLocalTranslation(x, y, z);
|
||||
parent.attachChild(geo);
|
||||
return geo;
|
||||
}
|
||||
|
||||
private BitmapText txt(String s, int size, ColorRGBA color) {
|
||||
BitmapText t = new BitmapText(font, false);
|
||||
t.setSize(size); t.setColor(color); t.setText(s);
|
||||
return t;
|
||||
}
|
||||
|
||||
private void centerText(BitmapText t, float x, float y, float width) {
|
||||
t.setLocalTranslation(x + (width - t.getLineWidth()) / 2f, y, 1);
|
||||
}
|
||||
|
||||
private boolean hits(Vector2f p, float x, float y, float w, float h) {
|
||||
return p.x >= x && p.x <= x + w && p.y >= y && p.y <= y + h;
|
||||
}
|
||||
}
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.font.BitmapFont;
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.input.MouseInput;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.MouseButtonTrigger;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
|
||||
public class PauseMenu extends BaseAppState {
|
||||
|
||||
private static final ColorRGBA COL_BG = new ColorRGBA(0.05f, 0.05f, 0.08f, 0.88f);
|
||||
private static final ColorRGBA COL_PANEL = new ColorRGBA(0.10f, 0.10f, 0.16f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN = new ColorRGBA(0.18f, 0.18f, 0.28f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN_DIS = new ColorRGBA(0.12f, 0.12f, 0.18f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN_QUIT = new ColorRGBA(0.38f, 0.10f, 0.10f, 1.00f);
|
||||
private static final ColorRGBA COL_TEXT = ColorRGBA.White;
|
||||
private static final ColorRGBA COL_TEXT_DIS = new ColorRGBA(0.40f, 0.40f, 0.40f, 1.00f);
|
||||
private static final ColorRGBA COL_TEXT_SUB = new ColorRGBA(0.35f, 0.35f, 0.35f, 1.00f);
|
||||
|
||||
private static final int BTN_GRAFIK = 0;
|
||||
private static final int BTN_AUDIO = 1;
|
||||
private static final int BTN_STEUERUNG = 2;
|
||||
private static final int BTN_BEENDEN = 3;
|
||||
|
||||
private SimpleApplication app;
|
||||
private Node guiNode;
|
||||
private BitmapFont font;
|
||||
private Node panel;
|
||||
|
||||
private Runnable onGraphics;
|
||||
private Runnable onControls;
|
||||
|
||||
// [x, y, w, h] per button
|
||||
private final float[][] btnBounds = new float[4][4];
|
||||
|
||||
public PauseMenu(Runnable onGraphics, Runnable onControls) {
|
||||
this.onGraphics = onGraphics;
|
||||
this.onControls = onControls;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
this.app = (SimpleApplication) app;
|
||||
this.guiNode = this.app.getGuiNode();
|
||||
this.font = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnable() {
|
||||
buildUI();
|
||||
app.getInputManager().setCursorVisible(true);
|
||||
app.getInputManager().addMapping("_PauseClick", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
|
||||
app.getInputManager().addListener(clickListener, "_PauseClick");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDisable() {
|
||||
if (panel != null) { guiNode.detachChild(panel); panel = null; }
|
||||
app.getInputManager().deleteMapping("_PauseClick");
|
||||
app.getInputManager().setCursorVisible(false);
|
||||
}
|
||||
|
||||
@Override protected void cleanup(Application app) {}
|
||||
|
||||
private void buildUI() {
|
||||
float sw = app.getCamera().getWidth();
|
||||
float sh = app.getCamera().getHeight();
|
||||
panel = new Node("pause-panel");
|
||||
addQuad(panel, 0, 0, sw, sh, COL_BG, -2);
|
||||
|
||||
float pw = 320, ph = 360;
|
||||
float px = (sw - pw) / 2f, py = (sh - ph) / 2f;
|
||||
addQuad(panel, px, py, pw, ph, COL_PANEL, -1);
|
||||
|
||||
BitmapText title = txt("PAUSE", 26, COL_TEXT);
|
||||
centerText(title, px, py + ph - 48, pw);
|
||||
panel.attachChild(title);
|
||||
|
||||
String[] labels = {"Grafik", "Audio", "Steuerung", "Beenden"};
|
||||
boolean[] enabled = {true, false, true, true};
|
||||
float bw = 260, bh = 52;
|
||||
float bx = px + (pw - bw) / 2f;
|
||||
float startY = py + ph - 112;
|
||||
float step = 62;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float by = startY - i * step;
|
||||
ColorRGBA bgCol = !enabled[i] ? COL_BTN_DIS : (i == BTN_BEENDEN ? COL_BTN_QUIT : COL_BTN);
|
||||
ColorRGBA txCol = enabled[i] ? COL_TEXT : COL_TEXT_DIS;
|
||||
|
||||
addQuad(panel, bx, by, bw, bh, bgCol, 0);
|
||||
|
||||
BitmapText lbl = txt(labels[i], 18, txCol);
|
||||
if (!enabled[i]) {
|
||||
// Center label in upper portion, show hint below
|
||||
lbl.setLocalTranslation(bx + (bw - lbl.getLineWidth()) / 2f, by + bh - 12, 1);
|
||||
BitmapText hint = txt("Bald verfügbar", 12, COL_TEXT_SUB);
|
||||
hint.setLocalTranslation(bx + (bw - hint.getLineWidth()) / 2f, by + 14, 1);
|
||||
panel.attachChild(hint);
|
||||
} else {
|
||||
centerText(lbl, bx, by + bh - 16, bw);
|
||||
}
|
||||
panel.attachChild(lbl);
|
||||
|
||||
btnBounds[i][0] = bx; btnBounds[i][1] = by;
|
||||
btnBounds[i][2] = bw; btnBounds[i][3] = bh;
|
||||
}
|
||||
|
||||
guiNode.attachChild(panel);
|
||||
}
|
||||
|
||||
private final ActionListener clickListener = (name, isPressed, tpf) -> {
|
||||
if (!isPressed) return;
|
||||
Vector2f c = app.getInputManager().getCursorPosition();
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (!hits(c, btnBounds[i][0], btnBounds[i][1], btnBounds[i][2], btnBounds[i][3])) continue;
|
||||
switch (i) {
|
||||
case BTN_GRAFIK -> { if (onGraphics != null) onGraphics.run(); }
|
||||
case BTN_AUDIO -> { /* Bald verfügbar */ }
|
||||
case BTN_STEUERUNG -> { if (onControls != null) onControls.run(); }
|
||||
case BTN_BEENDEN -> app.stop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private Geometry addQuad(Node parent, float x, float y, float w, float h, ColorRGBA color, float z) {
|
||||
Geometry geo = new Geometry("q", new Quad(w, h));
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", color.clone());
|
||||
if (color.a < 1f) {
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
geo.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
}
|
||||
geo.setMaterial(mat);
|
||||
geo.setLocalTranslation(x, y, z);
|
||||
parent.attachChild(geo);
|
||||
return geo;
|
||||
}
|
||||
|
||||
private BitmapText txt(String s, int size, ColorRGBA color) {
|
||||
BitmapText t = new BitmapText(font, false);
|
||||
t.setSize(size); t.setColor(color); t.setText(s);
|
||||
return t;
|
||||
}
|
||||
|
||||
private void centerText(BitmapText t, float x, float y, float width) {
|
||||
t.setLocalTranslation(x + (width - t.getLineWidth()) / 2f, y, 1);
|
||||
}
|
||||
|
||||
private boolean hits(Vector2f p, float x, float y, float w, float h) {
|
||||
return p.x >= x && p.x <= x + w && p.y >= y && p.y <= y + h;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,113 +1,113 @@
|
||||
package de.blight.game.control;
|
||||
|
||||
import com.jme3.bullet.control.CharacterControl;
|
||||
import com.jme3.input.InputManager;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.KeyTrigger;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.scene.Spatial;
|
||||
import de.blight.game.config.KeyBindings;
|
||||
|
||||
public class PlayerInputControl {
|
||||
|
||||
private static final float MOVE_SPEED = 0.07f;
|
||||
private static final float SPRINT_MULT = 1.5f;
|
||||
private static final float ROTATE_SPEED = 10f;
|
||||
|
||||
private static final String[] ACTION_NAMES =
|
||||
{"Forward", "Backward", "Left", "Right", "Jump", "Sprint"};
|
||||
|
||||
private final InputManager inputManager;
|
||||
private final Camera cam;
|
||||
|
||||
private CharacterControl physicsChar;
|
||||
private Spatial visual;
|
||||
|
||||
private boolean forward, backward, left, right, sprint;
|
||||
private boolean paused = false;
|
||||
|
||||
// Listener als Feld, damit er bei reload nicht doppelt registriert wird
|
||||
private final ActionListener actionListener = (name, isPressed, tpf) -> {
|
||||
if (paused) return;
|
||||
switch (name) {
|
||||
case "Forward" -> forward = isPressed;
|
||||
case "Backward" -> backward = isPressed;
|
||||
case "Left" -> left = isPressed;
|
||||
case "Right" -> right = isPressed;
|
||||
case "Sprint" -> sprint = isPressed;
|
||||
case "Jump" -> { if (isPressed && physicsChar != null) physicsChar.jump(); }
|
||||
}
|
||||
};
|
||||
|
||||
public PlayerInputControl(InputManager inputManager, Camera cam, KeyBindings kb) {
|
||||
this.inputManager = inputManager;
|
||||
this.cam = cam;
|
||||
registerMappings(kb);
|
||||
}
|
||||
|
||||
public void setPhysicsCharacter(CharacterControl physicsChar) {
|
||||
this.physicsChar = physicsChar;
|
||||
}
|
||||
|
||||
public void setVisual(Spatial visual) {
|
||||
this.visual = visual;
|
||||
}
|
||||
|
||||
public void setPaused(boolean paused) {
|
||||
this.paused = paused;
|
||||
if (paused) {
|
||||
forward = backward = left = right = sprint = false;
|
||||
if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
/** Löscht alte Mappings und registriert neue aus den übergebenen KeyBindings. */
|
||||
public void reloadBindings(KeyBindings kb) {
|
||||
for (String a : ACTION_NAMES) inputManager.deleteMapping(a);
|
||||
registerMappings(kb);
|
||||
// Zustand zurücksetzen, damit keine Taste „hängt"
|
||||
forward = backward = left = right = sprint = false;
|
||||
if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO);
|
||||
}
|
||||
|
||||
private void registerMappings(KeyBindings kb) {
|
||||
inputManager.addMapping("Forward", new KeyTrigger(kb.forward));
|
||||
inputManager.addMapping("Backward", new KeyTrigger(kb.backward));
|
||||
inputManager.addMapping("Left", new KeyTrigger(kb.left));
|
||||
inputManager.addMapping("Right", new KeyTrigger(kb.right));
|
||||
inputManager.addMapping("Jump", new KeyTrigger(kb.jump));
|
||||
inputManager.addMapping("Sprint", new KeyTrigger(kb.sprint));
|
||||
inputManager.addListener(actionListener, ACTION_NAMES);
|
||||
}
|
||||
|
||||
public void update(float tpf) {
|
||||
if (physicsChar == null || paused) return;
|
||||
|
||||
Vector3f camDir = cam.getDirection().clone().setY(0).normalizeLocal();
|
||||
Vector3f camLeft = cam.getLeft().clone().setY(0).normalizeLocal();
|
||||
|
||||
Vector3f moveDir = new Vector3f();
|
||||
if (forward) moveDir.addLocal(camDir);
|
||||
if (backward) moveDir.subtractLocal(camDir);
|
||||
if (left) moveDir.addLocal(camLeft);
|
||||
if (right) moveDir.subtractLocal(camLeft);
|
||||
|
||||
if (moveDir.lengthSquared() > 0.001f) {
|
||||
moveDir.normalizeLocal();
|
||||
float speed = sprint ? MOVE_SPEED * SPRINT_MULT : MOVE_SPEED;
|
||||
physicsChar.setWalkDirection(moveDir.mult(speed));
|
||||
|
||||
if (visual != null) {
|
||||
Quaternion targetRot = new Quaternion();
|
||||
targetRot.lookAt(moveDir, Vector3f.UNIT_Y);
|
||||
Quaternion current = visual.getLocalRotation().clone();
|
||||
current.slerp(targetRot, ROTATE_SPEED * tpf);
|
||||
visual.setLocalRotation(current);
|
||||
}
|
||||
} else {
|
||||
physicsChar.setWalkDirection(Vector3f.ZERO);
|
||||
}
|
||||
}
|
||||
}
|
||||
package de.blight.game.control;
|
||||
|
||||
import com.jme3.bullet.control.CharacterControl;
|
||||
import com.jme3.input.InputManager;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.KeyTrigger;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.scene.Spatial;
|
||||
import de.blight.game.config.KeyBindings;
|
||||
|
||||
public class PlayerInputControl {
|
||||
|
||||
private static final float MOVE_SPEED = 0.07f;
|
||||
private static final float SPRINT_MULT = 1.5f;
|
||||
private static final float ROTATE_SPEED = 10f;
|
||||
|
||||
private static final String[] ACTION_NAMES =
|
||||
{"Forward", "Backward", "Left", "Right", "Jump", "Sprint"};
|
||||
|
||||
private final InputManager inputManager;
|
||||
private final Camera cam;
|
||||
|
||||
private CharacterControl physicsChar;
|
||||
private Spatial visual;
|
||||
|
||||
private boolean forward, backward, left, right, sprint;
|
||||
private boolean paused = false;
|
||||
|
||||
// Listener als Feld, damit er bei reload nicht doppelt registriert wird
|
||||
private final ActionListener actionListener = (name, isPressed, tpf) -> {
|
||||
if (paused) return;
|
||||
switch (name) {
|
||||
case "Forward" -> forward = isPressed;
|
||||
case "Backward" -> backward = isPressed;
|
||||
case "Left" -> left = isPressed;
|
||||
case "Right" -> right = isPressed;
|
||||
case "Sprint" -> sprint = isPressed;
|
||||
case "Jump" -> { if (isPressed && physicsChar != null) physicsChar.jump(); }
|
||||
}
|
||||
};
|
||||
|
||||
public PlayerInputControl(InputManager inputManager, Camera cam, KeyBindings kb) {
|
||||
this.inputManager = inputManager;
|
||||
this.cam = cam;
|
||||
registerMappings(kb);
|
||||
}
|
||||
|
||||
public void setPhysicsCharacter(CharacterControl physicsChar) {
|
||||
this.physicsChar = physicsChar;
|
||||
}
|
||||
|
||||
public void setVisual(Spatial visual) {
|
||||
this.visual = visual;
|
||||
}
|
||||
|
||||
public void setPaused(boolean paused) {
|
||||
this.paused = paused;
|
||||
if (paused) {
|
||||
forward = backward = left = right = sprint = false;
|
||||
if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
/** Löscht alte Mappings und registriert neue aus den übergebenen KeyBindings. */
|
||||
public void reloadBindings(KeyBindings kb) {
|
||||
for (String a : ACTION_NAMES) inputManager.deleteMapping(a);
|
||||
registerMappings(kb);
|
||||
// Zustand zurücksetzen, damit keine Taste „hängt"
|
||||
forward = backward = left = right = sprint = false;
|
||||
if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO);
|
||||
}
|
||||
|
||||
private void registerMappings(KeyBindings kb) {
|
||||
inputManager.addMapping("Forward", new KeyTrigger(kb.forward));
|
||||
inputManager.addMapping("Backward", new KeyTrigger(kb.backward));
|
||||
inputManager.addMapping("Left", new KeyTrigger(kb.left));
|
||||
inputManager.addMapping("Right", new KeyTrigger(kb.right));
|
||||
inputManager.addMapping("Jump", new KeyTrigger(kb.jump));
|
||||
inputManager.addMapping("Sprint", new KeyTrigger(kb.sprint));
|
||||
inputManager.addListener(actionListener, ACTION_NAMES);
|
||||
}
|
||||
|
||||
public void update(float tpf) {
|
||||
if (physicsChar == null || paused) return;
|
||||
|
||||
Vector3f camDir = cam.getDirection().clone().setY(0).normalizeLocal();
|
||||
Vector3f camLeft = cam.getLeft().clone().setY(0).normalizeLocal();
|
||||
|
||||
Vector3f moveDir = new Vector3f();
|
||||
if (forward) moveDir.addLocal(camDir);
|
||||
if (backward) moveDir.subtractLocal(camDir);
|
||||
if (left) moveDir.addLocal(camLeft);
|
||||
if (right) moveDir.subtractLocal(camLeft);
|
||||
|
||||
if (moveDir.lengthSquared() > 0.001f) {
|
||||
moveDir.normalizeLocal();
|
||||
float speed = sprint ? MOVE_SPEED * SPRINT_MULT : MOVE_SPEED;
|
||||
physicsChar.setWalkDirection(moveDir.mult(speed));
|
||||
|
||||
if (visual != null) {
|
||||
Quaternion targetRot = new Quaternion();
|
||||
targetRot.lookAt(moveDir, Vector3f.UNIT_Y);
|
||||
Quaternion current = visual.getLocalRotation().clone();
|
||||
current.slerp(targetRot, ROTATE_SPEED * tpf);
|
||||
visual.setLocalRotation(current);
|
||||
}
|
||||
} else {
|
||||
physicsChar.setWalkDirection(Vector3f.ZERO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +1,93 @@
|
||||
package de.blight.game.control;
|
||||
|
||||
import com.jme3.input.InputManager;
|
||||
import com.jme3.input.MouseInput;
|
||||
import com.jme3.input.controls.AnalogListener;
|
||||
import com.jme3.input.controls.MouseAxisTrigger;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.scene.Spatial;
|
||||
|
||||
/**
|
||||
* Third-Person-Kamera:
|
||||
* - Mausbewegung → Kamera um den Charakter drehen (immer, kein Klick nötig)
|
||||
* - Y-Achse invertiert → Maus hoch = Kamera runter (oldschool)
|
||||
* - Mausrad → Zoom (Abstand)
|
||||
*/
|
||||
public class ThirdPersonCamera {
|
||||
|
||||
private static final float MOUSE_SENSITIVITY = 1.8f;
|
||||
private static final float MIN_DISTANCE = 3f;
|
||||
private static final float MAX_DISTANCE = 20f;
|
||||
private static final float MIN_VERTICAL_ANGLE = -0.3f;
|
||||
private static final float MAX_VERTICAL_ANGLE = FastMath.HALF_PI - 0.1f;
|
||||
private static final float TARGET_HEIGHT = 1.6f;
|
||||
|
||||
private final Camera cam;
|
||||
private final InputManager inputManager;
|
||||
|
||||
private Spatial target;
|
||||
|
||||
private float yaw = 0f;
|
||||
private float pitch = 0.4f;
|
||||
private float distance = 10f;
|
||||
private boolean paused = false;
|
||||
|
||||
public ThirdPersonCamera(Camera cam, InputManager inputManager) {
|
||||
this.cam = cam;
|
||||
this.inputManager = inputManager;
|
||||
registerMappings();
|
||||
}
|
||||
|
||||
public void setTarget(Spatial target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public void setPaused(boolean paused) { this.paused = paused; }
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private void registerMappings() {
|
||||
inputManager.addMapping("ZoomIn", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
|
||||
inputManager.addMapping("ZoomOut", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));
|
||||
inputManager.addMapping("MouseX", new MouseAxisTrigger(MouseInput.AXIS_X, false));
|
||||
inputManager.addMapping("MouseXNeg", new MouseAxisTrigger(MouseInput.AXIS_X, true));
|
||||
inputManager.addMapping("MouseY", new MouseAxisTrigger(MouseInput.AXIS_Y, false));
|
||||
inputManager.addMapping("MouseYNeg", new MouseAxisTrigger(MouseInput.AXIS_Y, true));
|
||||
|
||||
AnalogListener analogListener = (name, value, tpf) -> {
|
||||
if (paused) return;
|
||||
switch (name) {
|
||||
// Horizontale Rotation
|
||||
case "MouseX" -> yaw -= value * MOUSE_SENSITIVITY;
|
||||
case "MouseXNeg" -> yaw += value * MOUSE_SENSITIVITY;
|
||||
// Vertikale Rotation — Y invertiert: Maus hoch → Kamera runter
|
||||
case "MouseY" -> pitch = FastMath.clamp(pitch - value * MOUSE_SENSITIVITY, MIN_VERTICAL_ANGLE, MAX_VERTICAL_ANGLE);
|
||||
case "MouseYNeg" -> pitch = FastMath.clamp(pitch + value * MOUSE_SENSITIVITY, MIN_VERTICAL_ANGLE, MAX_VERTICAL_ANGLE);
|
||||
// Zoom
|
||||
case "ZoomIn" -> distance = FastMath.clamp(distance - value * 20f, MIN_DISTANCE, MAX_DISTANCE);
|
||||
case "ZoomOut" -> distance = FastMath.clamp(distance + value * 20f, MIN_DISTANCE, MAX_DISTANCE);
|
||||
}
|
||||
};
|
||||
inputManager.addListener(analogListener,
|
||||
"MouseX", "MouseXNeg", "MouseY", "MouseYNeg", "ZoomIn", "ZoomOut");
|
||||
}
|
||||
|
||||
public void update(float tpf) {
|
||||
if (target == null) return;
|
||||
|
||||
Vector3f pivot = target.getWorldTranslation().add(0, TARGET_HEIGHT, 0);
|
||||
|
||||
// Sphärische Koordinaten → kartesisch
|
||||
float x = distance * FastMath.cos(pitch) * FastMath.sin(yaw);
|
||||
float y = distance * FastMath.sin(pitch);
|
||||
float z = distance * FastMath.cos(pitch) * FastMath.cos(yaw);
|
||||
|
||||
Vector3f camPos = pivot.add(x, y, z);
|
||||
cam.setLocation(camPos);
|
||||
cam.lookAt(pivot, Vector3f.UNIT_Y);
|
||||
}
|
||||
|
||||
/** Aktueller Yaw-Winkel (für CharacterControl nutzbar). */
|
||||
public float getYaw() { return yaw; }
|
||||
}
|
||||
package de.blight.game.control;
|
||||
|
||||
import com.jme3.input.InputManager;
|
||||
import com.jme3.input.MouseInput;
|
||||
import com.jme3.input.controls.AnalogListener;
|
||||
import com.jme3.input.controls.MouseAxisTrigger;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.scene.Spatial;
|
||||
|
||||
/**
|
||||
* Third-Person-Kamera:
|
||||
* - Mausbewegung → Kamera um den Charakter drehen (immer, kein Klick nötig)
|
||||
* - Y-Achse invertiert → Maus hoch = Kamera runter (oldschool)
|
||||
* - Mausrad → Zoom (Abstand)
|
||||
*/
|
||||
public class ThirdPersonCamera {
|
||||
|
||||
private static final float MOUSE_SENSITIVITY = 1.8f;
|
||||
private static final float MIN_DISTANCE = 3f;
|
||||
private static final float MAX_DISTANCE = 20f;
|
||||
private static final float MIN_VERTICAL_ANGLE = -0.3f;
|
||||
private static final float MAX_VERTICAL_ANGLE = FastMath.HALF_PI - 0.1f;
|
||||
private static final float TARGET_HEIGHT = 1.6f;
|
||||
|
||||
private final Camera cam;
|
||||
private final InputManager inputManager;
|
||||
|
||||
private Spatial target;
|
||||
|
||||
private float yaw = 0f;
|
||||
private float pitch = 0.4f;
|
||||
private float distance = 10f;
|
||||
private boolean paused = false;
|
||||
|
||||
public ThirdPersonCamera(Camera cam, InputManager inputManager) {
|
||||
this.cam = cam;
|
||||
this.inputManager = inputManager;
|
||||
registerMappings();
|
||||
}
|
||||
|
||||
public void setTarget(Spatial target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public void setPaused(boolean paused) { this.paused = paused; }
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private void registerMappings() {
|
||||
inputManager.addMapping("ZoomIn", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
|
||||
inputManager.addMapping("ZoomOut", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));
|
||||
inputManager.addMapping("MouseX", new MouseAxisTrigger(MouseInput.AXIS_X, false));
|
||||
inputManager.addMapping("MouseXNeg", new MouseAxisTrigger(MouseInput.AXIS_X, true));
|
||||
inputManager.addMapping("MouseY", new MouseAxisTrigger(MouseInput.AXIS_Y, false));
|
||||
inputManager.addMapping("MouseYNeg", new MouseAxisTrigger(MouseInput.AXIS_Y, true));
|
||||
|
||||
AnalogListener analogListener = (name, value, tpf) -> {
|
||||
if (paused) return;
|
||||
switch (name) {
|
||||
// Horizontale Rotation
|
||||
case "MouseX" -> yaw -= value * MOUSE_SENSITIVITY;
|
||||
case "MouseXNeg" -> yaw += value * MOUSE_SENSITIVITY;
|
||||
// Vertikale Rotation — Y invertiert: Maus hoch → Kamera runter
|
||||
case "MouseY" -> pitch = FastMath.clamp(pitch - value * MOUSE_SENSITIVITY, MIN_VERTICAL_ANGLE, MAX_VERTICAL_ANGLE);
|
||||
case "MouseYNeg" -> pitch = FastMath.clamp(pitch + value * MOUSE_SENSITIVITY, MIN_VERTICAL_ANGLE, MAX_VERTICAL_ANGLE);
|
||||
// Zoom
|
||||
case "ZoomIn" -> distance = FastMath.clamp(distance - value * 20f, MIN_DISTANCE, MAX_DISTANCE);
|
||||
case "ZoomOut" -> distance = FastMath.clamp(distance + value * 20f, MIN_DISTANCE, MAX_DISTANCE);
|
||||
}
|
||||
};
|
||||
inputManager.addListener(analogListener,
|
||||
"MouseX", "MouseXNeg", "MouseY", "MouseYNeg", "ZoomIn", "ZoomOut");
|
||||
}
|
||||
|
||||
public void update(float tpf) {
|
||||
if (target == null) return;
|
||||
|
||||
Vector3f pivot = target.getWorldTranslation().add(0, TARGET_HEIGHT, 0);
|
||||
|
||||
// Sphärische Koordinaten → kartesisch
|
||||
float x = distance * FastMath.cos(pitch) * FastMath.sin(yaw);
|
||||
float y = distance * FastMath.sin(pitch);
|
||||
float z = distance * FastMath.cos(pitch) * FastMath.cos(yaw);
|
||||
|
||||
Vector3f camPos = pivot.add(x, y, z);
|
||||
cam.setLocation(camPos);
|
||||
cam.lookAt(pivot, Vector3f.UNIT_Y);
|
||||
}
|
||||
|
||||
/** Aktueller Yaw-Winkel (für CharacterControl nutzbar). */
|
||||
public float getYaw() { return yaw; }
|
||||
}
|
||||
|
||||
@@ -1,308 +1,388 @@
|
||||
package de.blight.game.scene;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.bullet.BulletAppState;
|
||||
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
|
||||
import com.jme3.bullet.control.CharacterControl;
|
||||
import com.jme3.bullet.control.RigidBodyControl;
|
||||
import com.jme3.bullet.util.CollisionShapeFactory;
|
||||
import com.jme3.light.*;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.*;
|
||||
import com.jme3.scene.shape.*;
|
||||
import com.jme3.shadow.*;
|
||||
import com.jme3.terrain.geomipmap.*;
|
||||
import com.jme3.texture.*;
|
||||
import com.jme3.util.SkyFactory;
|
||||
import de.blight.game.config.KeyBindings;
|
||||
import de.blight.game.control.PlayerInputControl;
|
||||
import de.blight.game.control.ThirdPersonCamera;
|
||||
|
||||
public class WorldScene extends BaseAppState {
|
||||
|
||||
private SimpleApplication app;
|
||||
private Node rootNode;
|
||||
private AssetManager assetManager;
|
||||
private BulletAppState bulletAppState;
|
||||
|
||||
private final KeyBindings keyBindings;
|
||||
private ThirdPersonCamera thirdPersonCam;
|
||||
private PlayerInputControl playerInput;
|
||||
|
||||
public WorldScene(KeyBindings keyBindings) {
|
||||
this.keyBindings = keyBindings;
|
||||
}
|
||||
|
||||
/** Wird von ConfigScreen nach dem Speichern aufgerufen. */
|
||||
public void reloadBindings(KeyBindings kb) {
|
||||
if (playerInput != null) playerInput.reloadBindings(kb);
|
||||
}
|
||||
|
||||
public void setPaused(boolean paused) {
|
||||
if (playerInput != null) playerInput.setPaused(paused);
|
||||
if (thirdPersonCam != null) thirdPersonCam.setPaused(paused);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Lifecycle
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
this.app = (SimpleApplication) app;
|
||||
this.rootNode = this.app.getRootNode();
|
||||
this.assetManager = app.getAssetManager();
|
||||
|
||||
bulletAppState = new BulletAppState();
|
||||
app.getStateManager().attach(bulletAppState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnable() {
|
||||
buildLighting();
|
||||
TerrainQuad terrain = buildTerrain();
|
||||
buildDecorations(terrain);
|
||||
|
||||
Node character = buildCharacter();
|
||||
rootNode.attachChild(character);
|
||||
|
||||
// Bullet-Charakter: Kapsel 0.4 Radius, 1.0 Höhe, Y-Achse (1)
|
||||
CapsuleCollisionShape capsule = new CapsuleCollisionShape(0.4f, 1.0f, 1);
|
||||
CharacterControl physicsChar = new CharacterControl(capsule, 0.05f);
|
||||
physicsChar.setJumpSpeed(12f);
|
||||
physicsChar.setFallSpeed(35f);
|
||||
physicsChar.setGravity(35f);
|
||||
physicsChar.setPhysicsLocation(new Vector3f(0, 5f, 0));
|
||||
character.addControl(physicsChar);
|
||||
bulletAppState.getPhysicsSpace().add(physicsChar);
|
||||
|
||||
playerInput = new PlayerInputControl(app.getInputManager(), app.getCamera(), keyBindings);
|
||||
playerInput.setPhysicsCharacter(physicsChar);
|
||||
playerInput.setVisual(character);
|
||||
|
||||
thirdPersonCam = new ThirdPersonCamera(app.getCamera(), app.getInputManager());
|
||||
thirdPersonCam.setTarget(character);
|
||||
|
||||
// Maus einfangen – keine Klick-Pflicht für Kamerasteuerung
|
||||
app.getInputManager().setCursorVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
playerInput.update(tpf);
|
||||
thirdPersonCam.update(tpf);
|
||||
}
|
||||
|
||||
@Override protected void cleanup(Application app) {}
|
||||
@Override protected void onDisable() {}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Beleuchtung
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private void buildLighting() {
|
||||
DirectionalLight sun = new DirectionalLight();
|
||||
sun.setDirection(new Vector3f(-0.5f, -1f, -0.5f).normalizeLocal());
|
||||
sun.setColor(ColorRGBA.White.mult(1.4f));
|
||||
rootNode.addLight(sun);
|
||||
|
||||
AmbientLight ambient = new AmbientLight();
|
||||
ambient.setColor(new ColorRGBA(0.3f, 0.3f, 0.4f, 1f));
|
||||
rootNode.addLight(ambient);
|
||||
|
||||
DirectionalLightShadowRenderer shadowRenderer =
|
||||
new DirectionalLightShadowRenderer(assetManager, 2048, 3);
|
||||
shadowRenderer.setLight(sun);
|
||||
shadowRenderer.setShadowIntensity(0.4f);
|
||||
app.getViewPort().addProcessor(shadowRenderer);
|
||||
|
||||
try {
|
||||
Spatial sky = SkyFactory.createSky(assetManager,
|
||||
"Textures/Sky/Bright/BrightSky.dds",
|
||||
SkyFactory.EnvMapType.CubeMap);
|
||||
rootNode.attachChild(sky);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Terrain
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private TerrainQuad buildTerrain() {
|
||||
int size = 257;
|
||||
float[] heights = new float[size * size];
|
||||
|
||||
for (int z = 0; z < size; z++) {
|
||||
for (int x = 0; x < size; x++) {
|
||||
float nx = x / (float) size;
|
||||
float nz = z / (float) size;
|
||||
heights[z * size + x] =
|
||||
FastMath.sin(nx * FastMath.TWO_PI * 2) * 2f
|
||||
+ FastMath.sin(nz * FastMath.TWO_PI * 3) * 1.5f
|
||||
+ FastMath.sin((nx + nz) * FastMath.TWO_PI * 1.5f) * 1f;
|
||||
}
|
||||
}
|
||||
|
||||
TerrainQuad terrain = new TerrainQuad("terrain", 65, size, heights);
|
||||
|
||||
Material terrainMat = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
|
||||
Texture grass = assetManager.loadTexture("Textures/gras.png");
|
||||
grass.setWrap(Texture.WrapMode.Repeat);
|
||||
terrainMat.setTexture("Tex1", grass);
|
||||
terrainMat.setFloat("Tex1Scale", 64f);
|
||||
|
||||
terrain.setMaterial(terrainMat);
|
||||
terrain.setLocalTranslation(0, -5f, 0);
|
||||
terrain.setLocalScale(0.5f, 0.5f, 0.5f);
|
||||
terrain.setShadowMode(RenderQueue.ShadowMode.Receive);
|
||||
|
||||
// Statischer Physics-Body (mass=0) für Terrain-Kollision
|
||||
RigidBodyControl terrainPhysics = new RigidBodyControl(
|
||||
CollisionShapeFactory.createMeshShape(terrain), 0f);
|
||||
terrain.addControl(terrainPhysics);
|
||||
bulletAppState.getPhysicsSpace().add(terrainPhysics);
|
||||
|
||||
rootNode.attachChild(terrain);
|
||||
return terrain;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Dekorationen – Höhe per TerrainQuad.getHeight() anpassen
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private void buildDecorations(TerrainQuad terrain) {
|
||||
Material stoneMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
stoneMat.setBoolean("UseMaterialColors", true);
|
||||
stoneMat.setColor("Diffuse", new ColorRGBA(0.55f, 0.55f, 0.55f, 1f));
|
||||
stoneMat.setColor("Ambient", new ColorRGBA(0.3f, 0.3f, 0.3f, 1f));
|
||||
stoneMat.setColor("Specular", ColorRGBA.White);
|
||||
stoneMat.setFloat("Shininess", 32f);
|
||||
|
||||
Material treeTrunkMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
treeTrunkMat.setBoolean("UseMaterialColors", true);
|
||||
treeTrunkMat.setColor("Diffuse", new ColorRGBA(0.45f, 0.28f, 0.1f, 1f));
|
||||
treeTrunkMat.setColor("Ambient", new ColorRGBA(0.2f, 0.12f, 0.04f, 1f));
|
||||
treeTrunkMat.setColor("Specular", ColorRGBA.Black);
|
||||
|
||||
Material treeTopMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
treeTopMat.setBoolean("UseMaterialColors", true);
|
||||
treeTopMat.setColor("Diffuse", new ColorRGBA(0.1f, 0.55f, 0.15f, 1f));
|
||||
treeTopMat.setColor("Ambient", new ColorRGBA(0.05f, 0.25f, 0.07f, 1f));
|
||||
treeTopMat.setColor("Specular", ColorRGBA.Black);
|
||||
|
||||
float[][] treeXZ = {
|
||||
{12, 8}, {-15, 5}, {20, -10}, {-8, -18},
|
||||
{5, 25}, {-22, 12}, {18, 20}, {-10, -5}
|
||||
};
|
||||
for (float[] xz : treeXZ) {
|
||||
float worldY = terrainWorldY(terrain, xz[0], xz[1]);
|
||||
Node tree = new Node("tree");
|
||||
|
||||
Geometry trunk = new Geometry("trunk", new Cylinder(8, 8, 0.25f, 2.5f, true));
|
||||
trunk.setMaterial(treeTrunkMat);
|
||||
trunk.rotate(FastMath.HALF_PI, 0, 0);
|
||||
trunk.setLocalTranslation(0, 1.25f, 0);
|
||||
trunk.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
|
||||
Geometry crown = new Geometry("crown", new Sphere(12, 12, 2.2f));
|
||||
crown.setMaterial(treeTopMat);
|
||||
crown.setLocalTranslation(0, 3.8f, 0);
|
||||
crown.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
|
||||
tree.attachChild(trunk);
|
||||
tree.attachChild(crown);
|
||||
tree.setLocalTranslation(xz[0], worldY, xz[1]);
|
||||
rootNode.attachChild(tree);
|
||||
}
|
||||
|
||||
float[][] stoneXZ = {{6, -6}, {-12, 15}, {16, -4}, {-3, 10}};
|
||||
for (float[] xz : stoneXZ) {
|
||||
float worldY = terrainWorldY(terrain, xz[0], xz[1]);
|
||||
float r = 0.6f + FastMath.nextRandomFloat() * 0.8f;
|
||||
Geometry stone = new Geometry("stone", new Sphere(8, 8, r));
|
||||
stone.setMaterial(stoneMat);
|
||||
stone.setLocalTranslation(xz[0], worldY + r * 0.5f, xz[1]);
|
||||
stone.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
rootNode.attachChild(stone);
|
||||
}
|
||||
}
|
||||
|
||||
/** Konvertiert Welt-XZ in lokale Terrain-Koordinaten und fragt die Höhe ab. */
|
||||
private float terrainWorldY(TerrainQuad terrain, float worldX, float worldZ) {
|
||||
Vector3f terrainTranslation = terrain.getWorldTranslation();
|
||||
Vector3f terrainScale = terrain.getWorldScale();
|
||||
float localX = (worldX - terrainTranslation.x) / terrainScale.x;
|
||||
float localZ = (worldZ - terrainTranslation.z) / terrainScale.z;
|
||||
float localH = terrain.getHeight(new Vector2f(localX, localZ));
|
||||
return terrainTranslation.y + localH * terrainScale.y;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Charakter (visueller Node, ohne eigenes Mesh für Physics)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private Node buildCharacter() {
|
||||
Node character = new Node("character");
|
||||
|
||||
Material bodyMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
bodyMat.setBoolean("UseMaterialColors", true);
|
||||
bodyMat.setColor("Diffuse", new ColorRGBA(0.2f, 0.4f, 0.8f, 1f));
|
||||
bodyMat.setColor("Ambient", new ColorRGBA(0.1f, 0.2f, 0.4f, 1f));
|
||||
bodyMat.setColor("Specular", ColorRGBA.White);
|
||||
bodyMat.setFloat("Shininess", 64f);
|
||||
|
||||
Material headMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
headMat.setBoolean("UseMaterialColors", true);
|
||||
headMat.setColor("Diffuse", new ColorRGBA(0.9f, 0.75f, 0.6f, 1f));
|
||||
headMat.setColor("Ambient", new ColorRGBA(0.45f, 0.37f, 0.3f, 1f));
|
||||
headMat.setColor("Specular", new ColorRGBA(0.2f, 0.2f, 0.2f, 1f));
|
||||
headMat.setFloat("Shininess", 16f);
|
||||
|
||||
Geometry body = new Geometry("body", new Cylinder(8, 16, 0.35f, 1.1f, true));
|
||||
body.setMaterial(bodyMat);
|
||||
body.rotate(FastMath.HALF_PI, 0, 0);
|
||||
body.setLocalTranslation(0, 0.9f, 0);
|
||||
body.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
|
||||
Geometry head = new Geometry("head", new Sphere(16, 16, 0.28f));
|
||||
head.setMaterial(headMat);
|
||||
head.setLocalTranslation(0, 1.75f, 0);
|
||||
head.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
|
||||
Geometry armL = buildLimb(bodyMat, 0.1f, 0.55f);
|
||||
armL.setLocalTranslation(-0.5f, 0.85f, 0);
|
||||
armL.rotate(0, 0, FastMath.DEG_TO_RAD * 20f);
|
||||
|
||||
Geometry armR = buildLimb(bodyMat, 0.1f, 0.55f);
|
||||
armR.setLocalTranslation(0.5f, 0.85f, 0);
|
||||
armR.rotate(0, 0, FastMath.DEG_TO_RAD * -20f);
|
||||
|
||||
Geometry legL = buildLimb(bodyMat, 0.12f, 0.6f);
|
||||
legL.setLocalTranslation(-0.18f, 0.3f, 0);
|
||||
|
||||
Geometry legR = buildLimb(bodyMat, 0.12f, 0.6f);
|
||||
legR.setLocalTranslation(0.18f, 0.3f, 0);
|
||||
|
||||
character.attachChild(body);
|
||||
character.attachChild(head);
|
||||
character.attachChild(armL);
|
||||
character.attachChild(armR);
|
||||
character.attachChild(legL);
|
||||
character.attachChild(legR);
|
||||
|
||||
return character;
|
||||
}
|
||||
|
||||
private Geometry buildLimb(Material mat, float radius, float height) {
|
||||
Geometry limb = new Geometry("limb", new Cylinder(6, 12, radius, height, true));
|
||||
limb.setMaterial(mat);
|
||||
limb.rotate(FastMath.HALF_PI, 0, 0);
|
||||
limb.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
return limb;
|
||||
}
|
||||
}
|
||||
package de.blight.game.scene;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.bullet.BulletAppState;
|
||||
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
|
||||
import com.jme3.bullet.control.CharacterControl;
|
||||
import com.jme3.bullet.control.RigidBodyControl;
|
||||
import com.jme3.bullet.util.CollisionShapeFactory;
|
||||
import com.jme3.light.*;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.*;
|
||||
import com.jme3.scene.shape.*;
|
||||
import com.jme3.shadow.*;
|
||||
import com.jme3.terrain.geomipmap.*;
|
||||
import com.jme3.texture.*;
|
||||
import com.jme3.util.SkyFactory;
|
||||
import de.blight.common.MapData;
|
||||
import de.blight.common.MapIO;
|
||||
import de.blight.game.config.KeyBindings;
|
||||
import de.blight.game.control.PlayerInputControl;
|
||||
import de.blight.game.control.ThirdPersonCamera;
|
||||
import de.blight.game.state.GrassState;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class WorldScene extends BaseAppState {
|
||||
|
||||
private SimpleApplication app;
|
||||
private Node rootNode;
|
||||
private AssetManager assetManager;
|
||||
private BulletAppState bulletAppState;
|
||||
private MapData loadedMapData;
|
||||
|
||||
private final KeyBindings keyBindings;
|
||||
private ThirdPersonCamera thirdPersonCam;
|
||||
private PlayerInputControl playerInput;
|
||||
private float spawnY = 5f; // wird in buildTerrain() gesetzt
|
||||
|
||||
public WorldScene(KeyBindings keyBindings) {
|
||||
this.keyBindings = keyBindings;
|
||||
}
|
||||
|
||||
/** Wird von ConfigScreen nach dem Speichern aufgerufen. */
|
||||
public void reloadBindings(KeyBindings kb) {
|
||||
if (playerInput != null) playerInput.reloadBindings(kb);
|
||||
}
|
||||
|
||||
public void setPaused(boolean paused) {
|
||||
if (playerInput != null) playerInput.setPaused(paused);
|
||||
if (thirdPersonCam != null) thirdPersonCam.setPaused(paused);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Lifecycle
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
this.app = (SimpleApplication) app;
|
||||
this.rootNode = this.app.getRootNode();
|
||||
this.assetManager = app.getAssetManager();
|
||||
|
||||
bulletAppState = new BulletAppState();
|
||||
app.getStateManager().attach(bulletAppState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnable() {
|
||||
buildLighting();
|
||||
TerrainQuad terrain = buildTerrain();
|
||||
buildDecorations(terrain);
|
||||
|
||||
if (loadedMapData != null) {
|
||||
app.getStateManager().attach(new GrassState(loadedMapData, terrain));
|
||||
}
|
||||
|
||||
Node character = buildCharacter();
|
||||
rootNode.attachChild(character);
|
||||
|
||||
// Bullet-Charakter: Kapsel 0.4 Radius, 1.0 Höhe, Y-Achse (1)
|
||||
CapsuleCollisionShape capsule = new CapsuleCollisionShape(0.4f, 1.0f, 1);
|
||||
CharacterControl physicsChar = new CharacterControl(capsule, 0.05f);
|
||||
physicsChar.setJumpSpeed(12f);
|
||||
physicsChar.setFallSpeed(35f);
|
||||
physicsChar.setGravity(35f);
|
||||
physicsChar.setPhysicsLocation(new Vector3f(0, spawnY, 0));
|
||||
character.addControl(physicsChar);
|
||||
bulletAppState.getPhysicsSpace().add(physicsChar);
|
||||
|
||||
playerInput = new PlayerInputControl(app.getInputManager(), app.getCamera(), keyBindings);
|
||||
playerInput.setPhysicsCharacter(physicsChar);
|
||||
playerInput.setVisual(character);
|
||||
|
||||
thirdPersonCam = new ThirdPersonCamera(app.getCamera(), app.getInputManager());
|
||||
thirdPersonCam.setTarget(character);
|
||||
|
||||
// Maus einfangen – keine Klick-Pflicht für Kamerasteuerung
|
||||
app.getInputManager().setCursorVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
playerInput.update(tpf);
|
||||
thirdPersonCam.update(tpf);
|
||||
}
|
||||
|
||||
@Override protected void cleanup(Application app) {}
|
||||
@Override protected void onDisable() {}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Beleuchtung
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private void buildLighting() {
|
||||
DirectionalLight sun = new DirectionalLight();
|
||||
sun.setDirection(new Vector3f(-0.5f, -1f, -0.5f).normalizeLocal());
|
||||
sun.setColor(ColorRGBA.White.mult(1.4f));
|
||||
rootNode.addLight(sun);
|
||||
|
||||
AmbientLight ambient = new AmbientLight();
|
||||
ambient.setColor(new ColorRGBA(0.3f, 0.3f, 0.4f, 1f));
|
||||
rootNode.addLight(ambient);
|
||||
|
||||
DirectionalLightShadowRenderer shadowRenderer =
|
||||
new DirectionalLightShadowRenderer(assetManager, 2048, 3);
|
||||
shadowRenderer.setLight(sun);
|
||||
shadowRenderer.setShadowIntensity(0.4f);
|
||||
app.getViewPort().addProcessor(shadowRenderer);
|
||||
|
||||
try {
|
||||
Spatial sky = SkyFactory.createSky(assetManager,
|
||||
"Textures/Sky/Bright/BrightSky.dds",
|
||||
SkyFactory.EnvMapType.CubeMap);
|
||||
rootNode.attachChild(sky);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Terrain
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Baut das Terrain. Falls eine gespeicherte Karte vorhanden ist, wird diese
|
||||
* geladen; andernfalls wird ein prozedurales Demo-Terrain erzeugt.
|
||||
* Setzt außerdem {@link #spawnY}.
|
||||
*/
|
||||
private TerrainQuad buildTerrain() {
|
||||
if (MapIO.exists()) {
|
||||
try {
|
||||
loadedMapData = MapIO.load();
|
||||
return buildTerrainFromMap(loadedMapData);
|
||||
} catch (IOException e) {
|
||||
System.err.println("[WorldScene] Karte nicht ladbar: " + e.getMessage()
|
||||
+ " — Fallback auf prozedurales Terrain.");
|
||||
}
|
||||
}
|
||||
return buildProceduralTerrain();
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt ein Terrain aus der gespeicherten {@link MapData}.
|
||||
* Die 4097×4097 Editor-Daten werden auf 513×513 heruntergesampelt
|
||||
* (jeder 8. Vertex), mit Scale (8, 1, 8) auf die gleiche Weltgröße
|
||||
* 4096 × 4096 WE gebracht.
|
||||
*/
|
||||
private TerrainQuad buildTerrainFromMap(MapData map) {
|
||||
final int GAME_VERTS = 513; // 512 Zellen à 8 WE = 4096 WE
|
||||
final int STEP = 8; // 4096 / 512 = 8 Vertices überspringen
|
||||
final int SRC_VERTS = MapData.TERRAIN_VERTS; // 4097
|
||||
|
||||
float[] heights = new float[GAME_VERTS * GAME_VERTS];
|
||||
for (int gz = 0; gz < GAME_VERTS; gz++) {
|
||||
int sz = gz * STEP;
|
||||
for (int gx = 0; gx < GAME_VERTS; gx++) {
|
||||
heights[gz * GAME_VERTS + gx] = map.terrainHeight[sz * SRC_VERTS + gx * STEP];
|
||||
}
|
||||
}
|
||||
|
||||
// Höhe in der Weltmitte als Spawn-Grundlage
|
||||
float centerHeight = heights[(GAME_VERTS / 2) * GAME_VERTS + (GAME_VERTS / 2)];
|
||||
spawnY = centerHeight + 3f;
|
||||
|
||||
TerrainQuad terrain = new TerrainQuad("terrain", 65, GAME_VERTS, heights);
|
||||
terrain.setLocalScale(8f, 1f, 8f); // 512 Zellen * 8 WE = 4096 WE pro Achse
|
||||
terrain.setShadowMode(RenderQueue.ShadowMode.Receive);
|
||||
|
||||
applyTerrainMaterial(terrain, 32f);
|
||||
|
||||
RigidBodyControl terrainPhysics = new RigidBodyControl(
|
||||
CollisionShapeFactory.createMeshShape(terrain), 0f);
|
||||
terrain.addControl(terrainPhysics);
|
||||
bulletAppState.getPhysicsSpace().add(terrainPhysics);
|
||||
|
||||
rootNode.attachChild(terrain);
|
||||
System.out.println("[WorldScene] Karte geladen, Spawn Y=" + spawnY);
|
||||
return terrain;
|
||||
}
|
||||
|
||||
/** Prozedurales Demo-Terrain als Fallback (keine gespeicherte Karte). */
|
||||
private TerrainQuad buildProceduralTerrain() {
|
||||
int size = 257;
|
||||
float[] heights = new float[size * size];
|
||||
for (int z = 0; z < size; z++) {
|
||||
for (int x = 0; x < size; x++) {
|
||||
float nx = x / (float) size;
|
||||
float nz = z / (float) size;
|
||||
heights[z * size + x] =
|
||||
FastMath.sin(nx * FastMath.TWO_PI * 2) * 2f
|
||||
+ FastMath.sin(nz * FastMath.TWO_PI * 3) * 1.5f
|
||||
+ FastMath.sin((nx + nz) * FastMath.TWO_PI * 1.5f) * 1f;
|
||||
}
|
||||
}
|
||||
|
||||
spawnY = 5f;
|
||||
|
||||
TerrainQuad terrain = new TerrainQuad("terrain", 65, size, heights);
|
||||
terrain.setLocalTranslation(0, -5f, 0);
|
||||
terrain.setLocalScale(0.5f, 0.5f, 0.5f);
|
||||
terrain.setShadowMode(RenderQueue.ShadowMode.Receive);
|
||||
|
||||
applyTerrainMaterial(terrain, 64f);
|
||||
|
||||
RigidBodyControl terrainPhysics = new RigidBodyControl(
|
||||
CollisionShapeFactory.createMeshShape(terrain), 0f);
|
||||
terrain.addControl(terrainPhysics);
|
||||
bulletAppState.getPhysicsSpace().add(terrainPhysics);
|
||||
|
||||
rootNode.attachChild(terrain);
|
||||
return terrain;
|
||||
}
|
||||
|
||||
private void applyTerrainMaterial(TerrainQuad terrain, float texScale) {
|
||||
Material mat = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
|
||||
try {
|
||||
Texture grass = assetManager.loadTexture("Textures/gras.png");
|
||||
grass.setWrap(Texture.WrapMode.Repeat);
|
||||
mat.setTexture("Tex1", grass);
|
||||
mat.setFloat("Tex1Scale", texScale);
|
||||
} catch (Exception e) {
|
||||
// Fallback: einfarbiges Material
|
||||
mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
mat.setBoolean("UseMaterialColors", true);
|
||||
mat.setColor("Diffuse", new ColorRGBA(0.28f, 0.58f, 0.18f, 1f));
|
||||
mat.setColor("Ambient", new ColorRGBA(0.15f, 0.30f, 0.09f, 1f));
|
||||
}
|
||||
terrain.setMaterial(mat);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Dekorationen – Höhe per TerrainQuad.getHeight() anpassen
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private void buildDecorations(TerrainQuad terrain) {
|
||||
Material stoneMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
stoneMat.setBoolean("UseMaterialColors", true);
|
||||
stoneMat.setColor("Diffuse", new ColorRGBA(0.55f, 0.55f, 0.55f, 1f));
|
||||
stoneMat.setColor("Ambient", new ColorRGBA(0.3f, 0.3f, 0.3f, 1f));
|
||||
stoneMat.setColor("Specular", ColorRGBA.White);
|
||||
stoneMat.setFloat("Shininess", 32f);
|
||||
|
||||
Material treeTrunkMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
treeTrunkMat.setBoolean("UseMaterialColors", true);
|
||||
treeTrunkMat.setColor("Diffuse", new ColorRGBA(0.45f, 0.28f, 0.1f, 1f));
|
||||
treeTrunkMat.setColor("Ambient", new ColorRGBA(0.2f, 0.12f, 0.04f, 1f));
|
||||
treeTrunkMat.setColor("Specular", ColorRGBA.Black);
|
||||
|
||||
Material treeTopMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
treeTopMat.setBoolean("UseMaterialColors", true);
|
||||
treeTopMat.setColor("Diffuse", new ColorRGBA(0.1f, 0.55f, 0.15f, 1f));
|
||||
treeTopMat.setColor("Ambient", new ColorRGBA(0.05f, 0.25f, 0.07f, 1f));
|
||||
treeTopMat.setColor("Specular", ColorRGBA.Black);
|
||||
|
||||
float[][] treeXZ = {
|
||||
{12, 8}, {-15, 5}, {20, -10}, {-8, -18},
|
||||
{5, 25}, {-22, 12}, {18, 20}, {-10, -5}
|
||||
};
|
||||
for (float[] xz : treeXZ) {
|
||||
float worldY = terrainWorldY(terrain, xz[0], xz[1]);
|
||||
Node tree = new Node("tree");
|
||||
|
||||
Geometry trunk = new Geometry("trunk", new Cylinder(8, 8, 0.25f, 2.5f, true));
|
||||
trunk.setMaterial(treeTrunkMat);
|
||||
trunk.rotate(FastMath.HALF_PI, 0, 0);
|
||||
trunk.setLocalTranslation(0, 1.25f, 0);
|
||||
trunk.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
|
||||
Geometry crown = new Geometry("crown", new Sphere(12, 12, 2.2f));
|
||||
crown.setMaterial(treeTopMat);
|
||||
crown.setLocalTranslation(0, 3.8f, 0);
|
||||
crown.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
|
||||
tree.attachChild(trunk);
|
||||
tree.attachChild(crown);
|
||||
tree.setLocalTranslation(xz[0], worldY, xz[1]);
|
||||
rootNode.attachChild(tree);
|
||||
}
|
||||
|
||||
float[][] stoneXZ = {{6, -6}, {-12, 15}, {16, -4}, {-3, 10}};
|
||||
for (float[] xz : stoneXZ) {
|
||||
float worldY = terrainWorldY(terrain, xz[0], xz[1]);
|
||||
float r = 0.6f + FastMath.nextRandomFloat() * 0.8f;
|
||||
Geometry stone = new Geometry("stone", new Sphere(8, 8, r));
|
||||
stone.setMaterial(stoneMat);
|
||||
stone.setLocalTranslation(xz[0], worldY + r * 0.5f, xz[1]);
|
||||
stone.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
rootNode.attachChild(stone);
|
||||
}
|
||||
}
|
||||
|
||||
/** Konvertiert Welt-XZ in lokale Terrain-Koordinaten und fragt die Höhe ab. */
|
||||
private float terrainWorldY(TerrainQuad terrain, float worldX, float worldZ) {
|
||||
Vector3f terrainTranslation = terrain.getWorldTranslation();
|
||||
Vector3f terrainScale = terrain.getWorldScale();
|
||||
float localX = (worldX - terrainTranslation.x) / terrainScale.x;
|
||||
float localZ = (worldZ - terrainTranslation.z) / terrainScale.z;
|
||||
float localH = terrain.getHeight(new Vector2f(localX, localZ));
|
||||
return terrainTranslation.y + localH * terrainScale.y;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Charakter (visueller Node, ohne eigenes Mesh für Physics)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private Node buildCharacter() {
|
||||
Node character = new Node("character");
|
||||
|
||||
Material bodyMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
bodyMat.setBoolean("UseMaterialColors", true);
|
||||
bodyMat.setColor("Diffuse", new ColorRGBA(0.2f, 0.4f, 0.8f, 1f));
|
||||
bodyMat.setColor("Ambient", new ColorRGBA(0.1f, 0.2f, 0.4f, 1f));
|
||||
bodyMat.setColor("Specular", ColorRGBA.White);
|
||||
bodyMat.setFloat("Shininess", 64f);
|
||||
|
||||
Material headMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
headMat.setBoolean("UseMaterialColors", true);
|
||||
headMat.setColor("Diffuse", new ColorRGBA(0.9f, 0.75f, 0.6f, 1f));
|
||||
headMat.setColor("Ambient", new ColorRGBA(0.45f, 0.37f, 0.3f, 1f));
|
||||
headMat.setColor("Specular", new ColorRGBA(0.2f, 0.2f, 0.2f, 1f));
|
||||
headMat.setFloat("Shininess", 16f);
|
||||
|
||||
Geometry body = new Geometry("body", new Cylinder(8, 16, 0.35f, 1.1f, true));
|
||||
body.setMaterial(bodyMat);
|
||||
body.rotate(FastMath.HALF_PI, 0, 0);
|
||||
body.setLocalTranslation(0, 0.9f, 0);
|
||||
body.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
|
||||
Geometry head = new Geometry("head", new Sphere(16, 16, 0.28f));
|
||||
head.setMaterial(headMat);
|
||||
head.setLocalTranslation(0, 1.75f, 0);
|
||||
head.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
|
||||
Geometry armL = buildLimb(bodyMat, 0.1f, 0.55f);
|
||||
armL.setLocalTranslation(-0.5f, 0.85f, 0);
|
||||
armL.rotate(0, 0, FastMath.DEG_TO_RAD * 20f);
|
||||
|
||||
Geometry armR = buildLimb(bodyMat, 0.1f, 0.55f);
|
||||
armR.setLocalTranslation(0.5f, 0.85f, 0);
|
||||
armR.rotate(0, 0, FastMath.DEG_TO_RAD * -20f);
|
||||
|
||||
Geometry legL = buildLimb(bodyMat, 0.12f, 0.6f);
|
||||
legL.setLocalTranslation(-0.18f, 0.3f, 0);
|
||||
|
||||
Geometry legR = buildLimb(bodyMat, 0.12f, 0.6f);
|
||||
legR.setLocalTranslation(0.18f, 0.3f, 0);
|
||||
|
||||
character.attachChild(body);
|
||||
character.attachChild(head);
|
||||
character.attachChild(armL);
|
||||
character.attachChild(armR);
|
||||
character.attachChild(legL);
|
||||
character.attachChild(legR);
|
||||
|
||||
return character;
|
||||
}
|
||||
|
||||
private Geometry buildLimb(Material mat, float radius, float height) {
|
||||
Geometry limb = new Geometry("limb", new Cylinder(6, 12, radius, height, true));
|
||||
limb.setMaterial(mat);
|
||||
limb.rotate(FastMath.HALF_PI, 0, 0);
|
||||
limb.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
return limb;
|
||||
}
|
||||
}
|
||||
|
||||
221
blight-game/src/main/java/de/blight/game/state/GrassState.java
Normal file
221
blight-game/src/main/java/de/blight/game/state/GrassState.java
Normal file
@@ -0,0 +1,221 @@
|
||||
package de.blight.game.state;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.scene.control.AbstractControl;
|
||||
import com.jme3.terrain.geomipmap.TerrainQuad;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import de.blight.common.MapData;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Rendert Gras im Spiel aus der in MapData gespeicherten Dichte-Map.
|
||||
*
|
||||
* Chunks werden gestreckt über mehrere Frames aufgebaut (INIT_PER_FRAME),
|
||||
* um Startlags zu vermeiden. GrassVisibilityControl cullt entfernte Chunks.
|
||||
*/
|
||||
public class GrassState extends BaseAppState {
|
||||
|
||||
// ── Konstanten (identisch mit PlacedObjectState im Editor) ────────────────
|
||||
private static final int TERRAIN_HALF = 2048;
|
||||
private static final float WORLD_SIZE = 4096f;
|
||||
private static final int SPLAT_SIZE = MapData.SPLAT_SIZE;
|
||||
private static final float SPLAT_WE_PER_PX = WORLD_SIZE / (SPLAT_SIZE - 1);
|
||||
private static final int CHUNK_SIZE = 128;
|
||||
private static final int CHUNKS_PER_AXIS = (TERRAIN_HALF * 2) / CHUNK_SIZE;
|
||||
private static final int CHUNK_COUNT = CHUNKS_PER_AXIS * CHUNKS_PER_AXIS;
|
||||
private static final int MAX_BLADES_PER_PX = 3;
|
||||
private static final float BLADE_WIDTH = 0.18f;
|
||||
private static final float DEFAULT_HEIGHT = 1.5f;
|
||||
private static final float FAR_DIST = 150f; // WE (game terrain is 1:1 WE)
|
||||
private static final float FAR_DIST_SQ = FAR_DIST * FAR_DIST;
|
||||
private static final int INIT_PER_FRAME = 4;
|
||||
|
||||
// ── Abhängigkeiten ────────────────────────────────────────────────────────
|
||||
private final MapData mapData;
|
||||
private final TerrainQuad terrain;
|
||||
|
||||
// ── Runtime-Zustand ───────────────────────────────────────────────────────
|
||||
private Camera cam;
|
||||
private Node grassNode;
|
||||
private Material grassMat;
|
||||
private int nextChunk = 0;
|
||||
|
||||
public GrassState(MapData mapData, TerrainQuad terrain) {
|
||||
this.mapData = mapData;
|
||||
this.terrain = terrain;
|
||||
}
|
||||
|
||||
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
this.cam = app.getCamera();
|
||||
grassNode = new Node("gameGrass");
|
||||
((SimpleApplication) app).getRootNode().attachChild(grassNode);
|
||||
grassMat = buildGrassMaterial(app.getAssetManager());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup(Application app) {
|
||||
((SimpleApplication) app).getRootNode().detachChild(grassNode);
|
||||
}
|
||||
|
||||
@Override protected void onEnable() { grassNode.setCullHint(Spatial.CullHint.Inherit); }
|
||||
@Override protected void onDisable() { grassNode.setCullHint(Spatial.CullHint.Always); }
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
int built = 0;
|
||||
while (nextChunk < CHUNK_COUNT && built < INIT_PER_FRAME) {
|
||||
buildChunk(nextChunk++);
|
||||
built++;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Material ──────────────────────────────────────────────────────────────
|
||||
|
||||
private Material buildGrassMaterial(AssetManager assets) {
|
||||
try {
|
||||
Material mat = new Material(assets, "MatDefs/Grass.j3md");
|
||||
mat.setColor("Color", new ColorRGBA(0.28f, 0.72f, 0.18f, 1f));
|
||||
mat.setFloat("WindSpeed", 0.5f);
|
||||
mat.setFloat("WindStrength", 0.14f);
|
||||
mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
|
||||
return mat;
|
||||
} catch (Exception e) {
|
||||
System.err.println("[GrassState] Grass.j3md nicht gefunden, Fallback: " + e.getMessage());
|
||||
Material mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", new ColorRGBA(0.25f, 0.65f, 0.15f, 1f));
|
||||
mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
|
||||
return mat;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Chunk aufbauen ────────────────────────────────────────────────────────
|
||||
|
||||
private void buildChunk(int idx) {
|
||||
int cx = idx % CHUNKS_PER_AXIS;
|
||||
int cz = idx / CHUNKS_PER_AXIS;
|
||||
float wXMin = -TERRAIN_HALF + cx * CHUNK_SIZE;
|
||||
float wZMin = -TERRAIN_HALF + cz * CHUNK_SIZE;
|
||||
|
||||
int pxMin = Math.max(0, (int)((wXMin + TERRAIN_HALF) / SPLAT_WE_PER_PX));
|
||||
int pzMin = Math.max(0, (int)((wZMin + TERRAIN_HALF) / SPLAT_WE_PER_PX));
|
||||
int pxMax = Math.min(SPLAT_SIZE - 1, (int)((wXMin + CHUNK_SIZE + TERRAIN_HALF) / SPLAT_WE_PER_PX));
|
||||
int pzMax = Math.min(SPLAT_SIZE - 1, (int)((wZMin + CHUNK_SIZE + TERRAIN_HALF) / SPLAT_WE_PER_PX));
|
||||
|
||||
List<float[]> blades = new ArrayList<>();
|
||||
Vector3f scale = terrain.getWorldScale();
|
||||
Vector3f trans = terrain.getWorldTranslation();
|
||||
|
||||
for (int pz = pzMin; pz <= pzMax; pz++) {
|
||||
for (int px = pxMin; px <= pxMax; px++) {
|
||||
int d = mapData.grassDensity[pz * SPLAT_SIZE + px] & 0xFF;
|
||||
if (d == 0) continue;
|
||||
int count = Math.max(1, (int)(d / 255f * MAX_BLADES_PER_PX));
|
||||
Random rng = new Random((long) px * 100003L + pz);
|
||||
float pixWorldX = px * SPLAT_WE_PER_PX - TERRAIN_HALF;
|
||||
float pixWorldZ = pz * SPLAT_WE_PER_PX - TERRAIN_HALF;
|
||||
for (int b = 0; b < count; b++) {
|
||||
float bx = pixWorldX + (rng.nextFloat() - 0.5f) * SPLAT_WE_PER_PX;
|
||||
float bz = pixWorldZ + (rng.nextFloat() - 0.5f) * SPLAT_WE_PER_PX;
|
||||
// Welt→lokal→Höhe→Welt
|
||||
float localX = (bx - trans.x) / scale.x;
|
||||
float localZ = (bz - trans.z) / scale.z;
|
||||
float th = terrain.getHeight(new Vector2f(localX, localZ));
|
||||
if (Float.isNaN(th)) continue;
|
||||
float worldY = trans.y + th * scale.y;
|
||||
float h = DEFAULT_HEIGHT * (0.7f + rng.nextFloat() * 0.6f);
|
||||
blades.add(new float[]{bx, worldY, bz, h});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (blades.isEmpty()) return;
|
||||
|
||||
Mesh mesh = buildGrassMesh(blades);
|
||||
float chunkCX = wXMin + CHUNK_SIZE * 0.5f;
|
||||
float chunkCZ = wZMin + CHUNK_SIZE * 0.5f;
|
||||
Geometry geo = new Geometry("grass_" + idx, mesh);
|
||||
geo.setMaterial(grassMat);
|
||||
geo.addControl(new GrassVisibilityControl(cam, new Vector3f(chunkCX, 0f, chunkCZ)));
|
||||
grassNode.attachChild(geo);
|
||||
}
|
||||
|
||||
// ── Mesh: Kreuz-Quad mit UV ───────────────────────────────────────────────
|
||||
|
||||
private static Mesh buildGrassMesh(List<float[]> blades) {
|
||||
int n = blades.size();
|
||||
FloatBuffer pos = BufferUtils.createFloatBuffer(n * 8 * 3);
|
||||
FloatBuffer uv = BufferUtils.createFloatBuffer(n * 8 * 2);
|
||||
IntBuffer idx = BufferUtils.createIntBuffer(n * 12);
|
||||
|
||||
int vi = 0;
|
||||
for (float[] blade : blades) {
|
||||
float x = blade[0], y = blade[1], z = blade[2], h = blade[3];
|
||||
float w = Math.max(0.05f, h * BLADE_WIDTH);
|
||||
|
||||
pos.put(x-w).put(y ).put(z); uv.put(0).put(0);
|
||||
pos.put(x+w).put(y ).put(z); uv.put(1).put(0);
|
||||
pos.put(x+w).put(y+h).put(z); uv.put(1).put(1);
|
||||
pos.put(x-w).put(y+h).put(z); uv.put(0).put(1);
|
||||
|
||||
pos.put(x).put(y ).put(z-w); uv.put(0).put(0);
|
||||
pos.put(x).put(y ).put(z+w); uv.put(1).put(0);
|
||||
pos.put(x).put(y+h).put(z+w); uv.put(1).put(1);
|
||||
pos.put(x).put(y+h).put(z-w); uv.put(0).put(1);
|
||||
|
||||
idx.put(vi ).put(vi+1).put(vi+2);
|
||||
idx.put(vi ).put(vi+2).put(vi+3);
|
||||
idx.put(vi+4).put(vi+5).put(vi+6);
|
||||
idx.put(vi+4).put(vi+6).put(vi+7);
|
||||
vi += 8;
|
||||
}
|
||||
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.setBuffer(VertexBuffer.Type.Position, 3, pos);
|
||||
mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, uv);
|
||||
mesh.setBuffer(VertexBuffer.Type.Index, 3, idx);
|
||||
mesh.updateBound();
|
||||
return mesh;
|
||||
}
|
||||
|
||||
// ── LOD-Control ───────────────────────────────────────────────────────────
|
||||
|
||||
private static final class GrassVisibilityControl extends AbstractControl {
|
||||
private final Camera cam;
|
||||
private final Vector3f center;
|
||||
|
||||
GrassVisibilityControl(Camera cam, Vector3f center) {
|
||||
this.cam = cam;
|
||||
this.center = center;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
float distSq = cam.getLocation().distanceSquared(center);
|
||||
spatial.setCullHint(distSq > FAR_DIST_SQ
|
||||
? Spatial.CullHint.Always
|
||||
: Spatial.CullHint.Inherit);
|
||||
}
|
||||
|
||||
@Override protected void controlRender(RenderManager rm, ViewPort vp) {}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user