The Noosa Game Engine
Relevant source files
The following files were used as context for generating this wiki page:- SPD-classes/src/main/java/com/watabou/noosa/Game.java
- SPD-classes/src/main/java/com/watabou/noosa/Scene.java
- SPD-classes/src/main/java/com/watabou/noosa/Visual.java
- SPD-classes/src/main/java/com/watabou/noosa/Camera.java
- SPD-classes/src/main/java/com/watabou/noosa/NoosaScript.java
- SPD-classes/src/main/java/com/watabou/input/InputHandler.java
- SPD-classes/src/main/java/com/watabou/noosa/RenderedText.java
- core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/GameScene.java
The Noosa Game Engine is a lightweight, 2D game engine that forms the foundational layer for Shattered Pixel Dungeon. It is built on top of the LibGDX framework and is responsible for the main game loop, scene management, input processing, and the entire rendering pipeline. The engine uses a custom OpenGL shader script for rendering all visual elements.
Noosa's architecture is centered around a few key classes: Game, which manages the application lifecycle; Scene, which represents different screens like the title screen or the main game; and Visual, the base class for any object that can be drawn to the screen. This structure provides a simple yet powerful scene graph for organizing and rendering game objects.
Core Architecture
The engine's core is the Game class, which implements the LibGDX ApplicationListener interface. This class orchestrates the entire application lifecycle, from creation and resizing to the main render loop.
Game Loop
The game loop is managed within the Game.render() method. It is responsible for clearing the screen, drawing the current scene, and stepping the game logic forward. A key architectural decision in Shattered Pixel Dungeon is the separation of the rendering thread from the game logic (actor) thread, which is managed within GameScene. The Noosa Game class itself handles the rendering loop and high-level updates.
The Game.update() method calculates the elapsed time since the last frame, processes all pending user input, and updates the current Scene.
Sources: SPD-classes/src/main/java/com/watabou/noosa/Game.java:125-141, 194-206
This diagram illustrates the main game loop managed by the Game class.
Scene Management
Scenes are the building blocks of the application's flow, representing different screens like the main menu, the game itself, or the leaderboards. The abstract Scene class is the parent for all such screens.
A Scene is a Group of Gizmo objects (the parent of Visual), effectively forming a scene graph. It manages the lifecycle of these objects through its create(), update(), draw(), and destroy() methods.
The Game class handles transitions between scenes. A call to Game.switchScene(Class<? extends Scene> c) requests a new scene. The actual switch happens at the beginning of the next step(), where the old scene is destroyed and the new one is created and initialized.
Sources: SPD-classes/src/main/java/com/watabou/noosa/Game.java:155-179, SPD-classes/src/main/java/com/watabou/noosa/Scene.java
The following diagram shows the lifecycle of a Scene.
Rendering System
Noosa's rendering system is built around Visual objects, a Camera, and a custom OpenGL shader script (NoosaScript).
Key Rendering Classes
| Class | Description | File Path |
|---|---|---|
Visual | The base class for all renderable objects. It contains properties for position, scale, rotation, color, and a transformation matrix. | SPD-classes/src/main/java/com/watabou/noosa/Visual.java |
Camera | Manages the viewport and projection matrix. It can follow a Visual and supports zooming and panning. It also handles scissor testing for rendering to a portion of the screen. | SPD-classes/src/main/java/com/watabou/noosa/Camera.java |
NoosaScript | A wrapper for the GLSL shader program used for rendering. It defines uniforms for camera/model matrices and color transformations, and attributes for vertex data. | SPD-classes/src/main/java/com/watabou/noosa/NoosaScript.java |
RenderedText | A specialized Visual for drawing text. It uses LibGDX's FreeType font generator and a custom Batch implementation to integrate with Noosa's rendering pipeline. | SPD-classes/src/main/java/com/watabou/noosa/RenderedText.java |
Sources: SPD-classes/src/main/java/com/watabou/noosa/Visual.java, SPD-classes/src/main/java/com/watabou/noosa/Camera.java, SPD-classes/src/main/java/com/watabou/noosa/NoosaScript.java, SPD-classes/src/main/java/com/watabou/noosa/RenderedText.java
Rendering Pipeline
The rendering process for a single Visual (like an Image or RenderedText) follows these steps:
- The
Scene'sdraw()method is called. - The
Sceneiterates through its children and callsdraw()on each visibleVisual. - The
Visual'sdraw()method updates its transformation matrix and callsNoosaScript. NoosaScriptis activated, and its uniforms (uCamera,uModel, color) are set.- The
Visual's vertex data (positions and texture coordinates) is passed to the shader. - The shader executes, transforming the vertices and coloring the fragments, rendering the object to the screen.
This diagram shows the sequence of a Visual object being rendered.
Shader (NoosaScript)
The engine uses a simple but effective GLSL shader managed by NoosaScript. The fragment shader is particularly important for the game's visual effects.
Fragment Shader Logic:
gl_FragColor = texture2D( uTex, vUV ) * uColorM + uColorA;
This formula allows for flexible color manipulation:
uColorM: A multiplicative color vector (rm, gm, bm, am). Used for tinting textures.uColorA: An additive color vector (ra, ga, ba, aa). Used for effects like making a sprite glow or appear white when hit.
Sources: SPD-classes/src/main/java/com/watabou/noosa/NoosaScript.java:144-154
Input Handling
Input is managed by the InputHandler class, which polls Gdx.input for events.
- In the main game loop,
Game.update()callsinputHandler.processAllEvents(). InputHandlercreates a list ofPointerEventobjects for each touch/mouse action.- The current
Scenereceives these events via itshandle(PointerEvent)method. - The
Scene(as aGroup) dispatches the event to its children.PointerAreaobjects (a type ofGizmo) can then react to clicks, drags, and hovers.
Sources: SPD-classes/src/main/java/com/watabou/input/InputHandler.java, SPD-classes/src/main/java/com/watabou/noosa/Game.java:202, SPD-classes/src/main/java/com/watabou/noosa/Scene.java:60-70
This diagram shows the flow of an input event.
Game and Logic Separation
A critical architectural pattern in Shattered Pixel Dungeon is the separation of game logic from the rendering and input loop. While Noosa manages the rendering thread, the core game logic (character actions, AI, turn progression) runs in a separate thread.
In GameScene.java, this is managed via a dedicated actorThread.
- The
GameScene.update()method checks if theactorThreadis running. If not, it starts it. - If the actor thread is waiting, the render thread notifies it to continue processing.
- This ensures that the UI remains responsive even when complex game logic is being calculated. The
Actor.process()method contains the main logic loop for the game's turn-based system.
Sources: core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/GameScene.java:363-393
Summary
The Noosa engine is a custom-built 2D engine that serves as the backbone of Shattered Pixel Dungeon. It leverages LibGDX for cross-platform support while implementing its own scene graph and a simple, efficient OpenGL rendering pipeline. Its key strengths are its lightweight nature and the clean separation it enables between rendering/input (managed by Noosa on the main thread) and core game logic (managed by the Actor system on a separate thread). This architecture is well-suited for a turn-based roguelike, ensuring a smooth user experience.