A small, modular game engine for Java built on LWJGL (OpenGL + GLFW).
It is a library, not a framework: your game owns main and the game loop, and picks the
pieces it needs by registering modules. There is no base class to extend.
Status: in development. Tested on macOS (arm64). The Linux/Windows native profiles are written but have not been exercised.
- Window and input (GLFW), behind small interfaces.
- A modular, pass-based OpenGL renderer: render passes, materials, depth/shadow mapping.
- A 2D toolkit: sprite sheets, bitmap text, a
Canvasfor 2D scenes and aUioverlay for HUDs. - A 3D toolkit: OBJ models, lit materials, shadow maps, cameras, projections and picking.
- An asset service with a texture cache.
- A tiny kernel (
Engine,Module,EngineContext) that wires everything together.
Requires JDK 17+. Maven is provided by the wrapper (./mvnw); LWJGL natives are picked
from your OS/architecture automatically.
./mvnw -DskipTests install # build and install all modules
# run the demo launcher (uses -XstartOnFirstThread on macOS automatically)
./mvnw -pl java-game-engine-demos exec:exec
# or a specific demo
./mvnw -pl java-game-engine-demos exec:exec \
-Dexec.mainClass=com.farias.jengine.examples.SnakeGameDemos read resources/... relative to the working directory, so they must run from
java-game-engine-demos/ (exec:exec already does that).
To smoke-run every demo for a few seconds each (it only checks that they open and stay up):
scripts/run-demos.sh # 5 s per demo
scripts/run-demos.sh -t 10 SnakeGame chess.IsometricChess| Demo | Shows |
|---|---|
SnakeGame |
2D with Canvas |
SpriteEditor |
2D pixel-art editor with Canvas and mouse input |
ModelViewer |
3D perspective scene + Ui overlay |
chess.IsometricChess |
3D isometric scene, picking, shadows + Ui overlay |
ShadowCubeDemo |
Engine + Renderer used directly, two-pass shadow mapping |
public static void main(String[] args) {
Engine engine = new Engine()
.registerModule(new GlfwPlatformModule(640, 480, "Hello")) // Window + Input
.registerModule(new RendererModule()) // Renderer (OpenGL)
.registerModule(new AssetsModule(Path.of("resources"))) // Assets
.registerModule(new Toolkit2DModule()); // Toolkit2D (Canvas, Ui)
EngineContext context = engine.start(); // cleans up after itself if a module fails
try {
Window window = context.require(Window.class);
Input input = context.require(Input.class);
Renderer renderer = context.require(Renderer.class);
Canvas canvas = context.require(Toolkit2D.class).canvas(320, 240);
Timer timer = new Timer();
while (!window.shouldClose() && !input.isKeyPressed(Key.ESCAPE)) {
window.pollEvents();
input.update(); // samples keys/mouse for this frame
timer.update();
// update(timer.deltaTime());
renderer.beginFrame();
canvas.begin();
canvas.textCentered(canvas.defaultFont(), "Hello", 160, 100);
renderer.endFrame(); // executes the passes and presents
}
} finally {
engine.terminate(); // reverse order of registration
}
}Coordinates in Canvas/Ui are virtual units (the size given to Toolkit2D.canvas/ui; each
surface has its own), origin at the top-left, Y grows downward, rotation in degrees
clockwise. canvas.toVirtualX/Y converts a mouse position to those units.
VSync is on by default; window.setVSync(false) uncaps the frame rate.
| Module | Responsibility | Depends on |
|---|---|---|
core |
Kernel: Engine, Module, EngineContext, Timer; contracts Window, Input, Presenter, Key, MouseButton |
nothing |
platform-glfw |
GlfwPlatformModule: window + input through GLFW |
core |
renderer |
Renderer, RendererModule, passes, materials, meshes, shaders, textures (OpenGL) |
core |
assets |
Assets, AssetsModule: path resolution and texture cache |
core, renderer |
toolkit2d |
Toolkit2D (creates Canvas and Ui), SpriteSheet, BitmapFont, Toolkit2DModule |
core, renderer, assets |
toolkit3d |
Shapes, LitMaterials, Models, Shadows, cameras, picking, Toolkit3DModule |
core, renderer, assets |
demos |
Example games | all of the above |
Maven artifacts and directories are java-game-engine-<module> (group com.farias.jengine); each module's
code lives in the package com.farias.jengine.<module> (platform-glfw in platform.glfw, and the
Window/Input contracts of core in platform).
Services each module registers (what context.require(...) can return):
| Module class | Services |
|---|---|
GlfwPlatformModule |
Window, Input, Presenter |
RendererModule |
Renderer |
AssetsModule |
Assets |
Toolkit2DModule |
Toolkit2D, SpriteRenderer (low level) |
Toolkit3DModule |
Shapes, LitMaterials, Models, Shadows |
graph BT
core
platform-glfw --> core
renderer --> core
assets --> core
assets --> renderer
toolkit2d --> core
toolkit2d --> renderer
toolkit2d --> assets
toolkit3d --> core
toolkit3d --> renderer
toolkit3d --> assets
demos --> platform-glfw
demos --> toolkit2d
demos --> toolkit3d
These rules are the architectural proposal of the engine. Breaking one should be a deliberate decision, not an accident. The reasoning behind each rule is in DEVELOPMENT.md.
- Library, not framework: the game owns
mainand the loop; there is no base class to extend. - Composition through modules: the game registers the modules it needs; services come from
EngineContext.require(...). - Components with dependencies come from modules: the game does not
newthem (their constructors are not public);staticis for pure functions only. - Dependency direction:
coredepends on nothing; library modules never depend onplatform-*;toolkit2dandtoolkit3dnever depend on each other. - Platform is a contract:
Window,InputandPresenterare interfaces incore, implemented byplatform-*modules. The renderer presents throughPresenter, so it never sees the window. - One frame lifecycle:
pollEvents→input.update→beginFrame→ passes →endFrame; overlays are begun after the scene. - Resource ownership: the
Rendererowns GPU resources (only it can create them, anddestroyfrees one early);Assetscaches textures andModelscaches meshes. - Conventions do not leak: clip space, UV and winding stay inside the toolkits.
- Where new code goes: graphics API →
renderer; OS library →platform-*; 2D-only or 3D-only → its toolkit; anything with a service → a module.
- The renderer is OpenGL-only:
Mesh,ShaderandTextureare concrete OpenGL classes and theRendererbinds programs, sets uniforms by name and draws directly. A second backend would need opaque resource handles plus aRenderBackendSPI that executes a whole frame (execute(frame)); that is intentionally not built until it is actually needed. - The OpenGL 3.3 core context request still lives in
platform-glfw. - Shader sources are GLSL 330 strings inside
toolkit2dandtoolkit3d. Matrix4f(JOML) appears in the renderer API.
DEVELOPMENT.md: module details, build decisions, lessons from past bugs and open items (pt-BR).java-game-engine-demos/CHESS_MODELS.md: why the chess models needflipNormals.