This is a library written in Kotlin to be used in conjunction with libGDX (https://github.com/libgdx/libgdx) to be able to render particle effects with Effekseer (https://github.com/effekseer/Effekseer).
Currently based on versions of:
- Effekseer:
1.70e
- libGDX:
1.12.1
- roboVM:
2.3.20
- used for iOS
Basic example that loads, renders, and modifies the effect's color properties at runtime every few seconds:
GDXseer.basic.example.mp4
Based on https://github.com/SrJohnathan/gdx-effekseer.
All shown source code below is in Kotlin. All shown gradle script content uses the Kotlin DSL API.
- Windows (OpenGL)
- MacOS (OpenGL)
- Android (OpenGLES)
- iOS (OpenGLES or Metal)
- Currently doesn't render the particle effects on Metal, but you can still build the library for Metal so that you can include it with the Metal backend for libGDX for testing purposes. Why doesn't it work for Metal? Cause I'm dum... Someone please halp. I haven't figured out how to use Effekseer Metal with metalAngle that GDX robovm metal backend uses.
- Linux (Might already work without much if any changes, but I don't have a Linux distribution installed to build this)
Make sure you're using libGDX version 1.12.0.
api("io.github.niraj-rayalla:GDXseer:1.0.0")
api("io.github.niraj-rayalla:GDXseer-GL:1.0.0")
api("io.github.niraj-rayalla:GDXseer-desktop:1.0.0")
val native by configurations.creating
android {
dependencies {
api("io.github.niraj-rayalla:GDXseer-GL:1.0.0")
api("io.github.niraj-rayalla:GDXseer-android:1.0.0")
native("io.github.niraj-rayalla:GDXseer-android:1.0.0")
}
}
val copyEffekseerNatives by tasks.creating {
dependsOn(createNativesLibDirectories)
doFirst {
native.copy().files.forEach { nativesFile ->
if (nativesFile.name.endsWith("GDXseer-android-1.0.0.jar")) {
println("Copying Effekseer natives from jar: ${nativesFile.name}")
zipTree(nativesFile).files.forEach { file ->
var wasCopied = false
if (file.path.contains("arm64-v8a")) {
wasCopied = true
copy {
from(file.path).into("libs/arm64-v8a").include("*.so")
}
}
if (file.path.contains("armeabi-v7a")) {
wasCopied = true
copy {
from(file.path).into("libs/armeabi-v7a").include("*.so")
}
}
if (file.path.contains("x86")) {
wasCopied = true
copy {
from(file.path).into("libs/x86").include("*.so")
}
}
if (file.path.contains("x86_64")) {
wasCopied = true
copy {
from(file.path).into("libs/x86_64").include("*.so")
}
}
if (wasCopied) {
println("Copied Effekseer native files at path: ${file.path} natives from jar: ${nativesFile.name}")
}
}
}
}
}
}
tasks.whenTaskAdded {
if (this.name.contains("package")) {
this.dependsOn(copyEffekseerNatives)
}
}
If you're using RoboVM backend:
api("io.github.niraj-rayalla:GDXseer-GL:1.0.0")
api("io.github.niraj-rayalla:GDXseer-ios-GL:1.0.0")
OR
If you're using RoboVM-Metal backend:
api("io.github.niraj-rayalla:GDXseer-Metal:1.0.0")
api("io.github.niraj-rayalla:GDXseer-ios-Metal:1.0.0-WIP")
If you're using RoboVM backend:
<frameworks>
<framework>OpenGLES</framework>
</frameworks>
OR
If you're using RoboVM-Metal backend:
<frameworks>
<framework>Metal</framework>
</frameworks>
- Call
GDXseer.init()
in the core libGDX module during the creation of the app. - Set the GDX asset loaders used to load the assets used by Effekseer in a GDX manner:
val assetManager = AssetManager(fileHandleResolver) assetManager.apply { setLoader(EffekseerParticleSubAssetLoader.Companion.Result::class.java, null, EffekseerParticleSubAssetLoader(this.fileHandleResolver)) setLoader(EffekseerParticleAssetLoader.Companion.Result::class.java, null, EffekseerParticleAssetLoader(this.fileHandleResolver)) // Optionally pass in an instance of EffekseerIsMipMapEnabledDecider to override if an effect texture should use mimaps. By default all textures use mipmaps. setLoader(EffekseerParticleAssetLoader.Companion.Result::class.java, null, EffekseerParticleAssetLoader(this.fileHandleResolver, object: EffekseerIsMipMapEnabledDecider { override fun isMipMapEnabledForTextureFile(textureFileHandle: FileHandle): Boolean { ... } })) }
- Create and initialize the GDXseer manager object:
// For GL val gdxseerManager = GDXseerGLManager(1000, true, renderContext, OpenGLDeviceType.OpenGLES3) // For Metal val gdxseerManager = GDXseerMetalManager(1000, true, renderContext) // Initialize the manager gdxseerManager.initializeAll() -- OR -- Look at GDXseerManager initialize steps for more info gdxseerManager.initializeStep1CreateManagerAdapter() gdxseerManager.initializeStep2CreateRenderer() gdxseerManager.initializeStep3CreateSubRenderers() gdxseerManager.initializeStep4CreateLoaders() gdxseerManager.initializeStep5Finish()
- Load particle effects by doing the following:
val particleEffect = GDXseerParticleEffect(gdxseerManager) // For immediate loading particleEffect.syncLoad(assetManager, "data/tu.efk") // For asynchronous loading particleEffect.asyncLoad(assetManager, "data/tu.efk", object: GDXseerParticleEffect.Companion.LoadingListener { override fun onEffectSuccessfullyLoaded(gdXseerParticleEffect: GDXseerParticleEffect) { ... } override fun onEffectFailedToLoad(gdXseerParticleEffect: GDXseerParticleEffect, throwable: Throwable?) { ... } })
- Play, modify, and stop effects by doing the following:
particleEffect.play() // Do any operations such as setting the color (particleEffect.rootNode.getChild(0) as GDXseerEffectNodeSprite).fixed.value = io.github.niraj_rayalla.gdxseer.effekseer.Color(r, g, b, a) particleEffect.stop()
- Rendering the particle effects:
fun render() { ... manager.draw(Gdx.graphics.getDeltaTime()) --OR-- manager.update(Gdx.graphics.getDeltaTime()) // Can call this draw as many times as needed. For example one draw for post-processing and then the normal draw. manager.drawAfterUpdate() }
- Dispose the effects and manager when no longer needed:
// Dispose a single effect particleEffect.dispose() // Dispose entire manager gdxseerManager.dispose()
- When using the GL renderer on iOS, and creating GDXseerGLManager with OpenGLDeviceType.OpenGLES3 causes a crash when rendering. Using OpenGLDeviceType.OpenGLES2 doesn't cause the crash. I'm don't have any clue as to why this happens.
Look in path /examples
for all the example sub-projects available.
Currently there's just one example called Basic
with one sub-project for each of: desktop, Android, and iOS.
It shows how to initialize GDXseer, load a particle effect, render it, and also change color properties at runtime every few seconds.
This repository also includes IntelliJ run configurations for quickly running each of the examples sub-projects.
If you want to build the libraries yourself, follow these steps, or not, your choice:
- Install latest Swig (https://www.swig.org/download.html)
- Install Cmake (https://cmake.org/download/) and the desired C++ compiler, such as g++
- Clone this repository
- Run
git submodule update --init --recursive
to pull the Effekseer repository along with all its submodules. - Run
./gradlew publishToMavenLocal
to build all sub-projects, or call one of the specific build Gradle tasks such as./gradlew :buildDesktopLibrary
. The following are all possible build tasks:buildDesktopLibrary
buildAndroidLibrary
buildIOSGLLibrary
buildIOSMetalLibrary
- For IOS building, set
iOS_JAVA_HOME
(optional) andiOS_CODE_SIGN_IDENTITY
in local.properties file.iOS_JAVA_HOME
line should point to the JDK path to use for iOS building and be like thisiOS_JAVA_HOME=/Users/username/jdk-13.0.2.jdk/Contents/Home/
. If not providedJAVA_HOME
is used. JDK 13 is used for the built libraries.iOS_CODE_SIGN_IDENTITY
is the identity you would pass tocodesign -s ... framework
to sign the iOS frameworks.
How is the project structured? I would like to know as well. Nvm, it's right here:
- The root module contains the common GDXseer logic as well as the Swig generated Java of the core Effekseer logic which all the sub-modules use.
- Then, there are two modules that directly use the root module
GL
andMetal
, which contain the Swig generated and written Kotlin classes for the renderer being used. - Finally, we have the modules that build into the library (jar) file, which depend on
GL
orMetal
module:GDXseer-desktop
GDXseer-android
GDXseer-ios-GL
GDXseer-ios-Metal
Contains tasks (named above) that build the Effekseer C++ code, bridge C++ code for GDXseer, for the appropriate platform, and then build the jar file that contains the built shared library. Also contains Maven publishing tasks.
Contains the Swig interface files, that determine how Swig should generate the Java wrapper classes from the C++ code. The highest level Swig interface files are:
effekseer.i
which generates/cpp/Effekseer_Swig.cpp
and the Java classes in/src/main/java/io/github/niraj_rayalla/gdxseer/effekseer/
adapter_effekseer.i
which generates/cpp/Adapter_Effekseer_Swig.cpp
and the Java classes in/src/main/java/io/github/niraj_rayalla/gdxseer/adapter_effekseer/
effekseer_GL.i
which generates/cpp/Effekseer_GL_Swig.cpp
and the Java classes in/GL/src/main/java/io/github/niraj_rayalla/gdxseer/effekseer_gl/
effekseer_Metal.i
which generates/cpp/Effekseer_Metal_Swig.mm
and the Java classes in/Metal/src/main/java/io/github/niraj_rayalla/gdxseer/effekseer_metal/
Used to build the swig generated C++ and the non-generated Effekseer C++ logic.
Builds into one of these directories for the corresponding platform being built for:
/cmake-windows/
/cmake-macos/
/cmake-linux/
/android-build/
/cmake-ios-device-gl/
/cmake-ios-device-metal/
/cmake-ios-simulator-gl/
/cmake-ios-simulator-metal/
Each module is a JVM library. When running the :jar
task for that module, it builds using the Swig generation, and Cmake building described above.
Then, the jar is built with the built C++ shared libraries included in it.
For iOS, the shared library files are not included directly in the jar like for the other platforms. Instead the frameworks for device and simulator are created from the built shared libraries first, then the XCFramework from those frameworks, and that is what's included in the jar file.
If you would like to contribute to this project, please feel free to send pull requests.