A zero dependency, lightweight Java game engine for making 2D games inspired by Unity and Libgdx. It is built on top of java swing / awt. Decaf makes it especially easy to build pixel art games.
🚧 Warning: This project is still a work-in-progress
Features:
-
GameObjects and Components
-
Scene management
-
Time management (cap FPS, delta time, ...)
-
2d Camera system
-
Keyboard and Mouse APIs
-
Sound handling for music and sound effects
-
2d collision detection and resolution engine for axis-aligned rectangles (with support for layers)
-
Maths
-
Vector2 type
-
Easy to use Random utility
-
Similarly to Unity, most objects (entities) in your game are represented by GameObjects. They themselves don't generally implement behaviour (such as rendering or collision handling), but they instead act as a container for components. Each feature / functionality an object should have is implemented in a separate component.
GameObject go = new GameObject();
go.addComponent(new Transform());
Creating custom components:
public class PlayerController extends Component implements IUpdatable {
private Transform transform;
@Override
public void onStart() {
System.out.println("Just added to a GameObject");
transform = (Transform)getComponent(Transform.class);
}
@Override
public void onUpdate() {
// Moves the player with the speed of 1 unit per second to the right
transform.setPosition(
transform.getPosition().add(1, 0).mul(Time.deltaTime())
);
}
}
Adding your custom component:
// Adding a component instantly registers it in the game event loop
go.addComponent(new PlayerController());
Removing components:
Transform transform = (Transform)go.getComponent(Transform.class);
go.removeComponent(transform);
Destroying a GameObject:
// This removes the GameObject and its components from the game loop
go.destroy();
Most of the GameObject's method are also wrapped inside the Component
class, meaning that you can directly use methods such as addComponent
, getComponent
, hasComponent
, removeComponent
from within a component and don't have to explicitly call them on the GameObject.
GameObjects also let you add tags:
go.addTag("Player");
boolean hasTag = go.hasTag("Player");
go.removeTag("Player");
Decaf also has an easy to use, polling-based API for querying the Keyboard and Mouse state. During the period of one frame, the state of the mouse and keyboard will be the same.
// Is the W key currently being held down?
Keyboard.isKeyDown(Keys.W);
Keyboard.isKeyDown(Keys.ALPHA0);
// Did the User just press the space key down?
Keyboard.isKeyJustDown(Keys.SPACE);
// Did the User just release the space key?
Keyboard.isKeyJustUp(Keys.SPACE);
Mouse buttons:
Mouse.isButtonDown(MouseButtons.LEFT);
Mouse.isButtonJustDown(MouseButtons.MIDDLE);
Mouse.istButtonJustUp(MouseButtons.RIGHT);
Mouse wheel:
float scrollAmount = Mouse.getScrollAmount();
Mouse position:
Vector2 screenPosition = Mouse.getPosition();
Vector2 worldPosition = Camera.main().screenToWorldPos(screenPosition);
There are two types of sounds:
-
Sound effects are short clips of sound that can be fully loaded into memory. E.g. gun shots, explosions, ... Multiple of them can also be played in parallel and they are represented by the
SoundEffect
class. -
Music is for longer clips of sound that are too large to be loaded into memory at once. E.g. music (40MB), long voice lines, ... The music data is then streamed from the disk into memory as it is needed. This type of sound is represented by the
Music
class.
On top of this, the pan and volume can be adjusted to make the audio sound 3d. These values can be calculated using the SpatialAudio
class.
At the beginning of the game, when you load all your assets, you can create a new SoundEffect
instance, passing in a file path:
SoundEffect gunShot = new SoundEffect("./assets/GunShot.wav");
If you want, you can customise the master volume:
// 0 => Silent
// 1 => Normal volume
// 2 => Twice as loud
gunShot.setMasterVolume(1);
Later, when you want to play the sound effect, you can do the following:
gunShot.play();
// Or
gunShot.repeat(5);
// Or
gunShot.repeatForever();
If you want to stop the playing instance, or customise the volume / pan (how far left / right the sound is perceived), you can use the returned SoundEffectInstance
:
SoundEffectInstance sfx = gunShot.play();
sfx.setVolume(0.5f);
// -1 => left
// 0 => middle
// 1 => right
sfx.setPan(-0.5f);
sfx.stop();
After you've stopped a sound effect from being played, it can not be restarted or resumed, as the internal resources are automatically cleaned up.
Similarly to sound effects, you should only create a Music
instance for each sound at the start of the game and then this instance should be reused.
Music music = new Music("./assets/AmbientMusic.wav");
music.setVolume(0.5f);
// Starts / Restarts the song
music.play();
music.pause();
// Continues playing
music.resume();
The SpatialAudio
class provides some methods for setting the pan (the left-right positioning of the audio) and setting the volume of sound effects to achieve a quasi-3d experience.
By default is uses the Camera.main()
to get the main camera which is assumed be the audio listener in the scene.
Vector2 pos = new Vector2(0.5f, 2);
float pan = SpatialAudio.getPanForWorldPos(pos);
float volume = SpatialAudio.getVolumeForWorldPos(pos);
// sfx is a SoundEffectInstance
sfx.setPan(pan);
sfx.setVolume(volume);