Welcome to Zeta’s documentation!¶
Zeta is an open-source top-down fully moddable 2D RPG singleplayer game engine, written in Java using LibGDX, under the Apache 2.0 License. Games are written in Java as mods to the base engine, which ensures that every aspect of Zeta can be tweaked to the design of your project.
Zeta is written using LibGDX, an open-source Java-based game development framework created by badlogicgames licensed under the Apache License 2.0.
Setup¶
This guide will show you how to set up a project with the Zeta engine.
Compiling the Zeta library¶
Zeta uses the Gradle build tool. If you’re on Windows, open a command line and run ./gradlew desktop:dist
in Zeta’s main folder. If you’re on a Unix system, run ./gradle desktop:dist
instead. This will create the zeta-1.0.jar
binary, which will be located in the desktop/build/libs
folder.
Writing code with Zeta¶
In your IDE, make sure to add the Zeta jar file as a dependency to your project so that it can detect Zeta’s functions and provide hints. Alternatively, link it to Zeta’s source code so that you also get quick access to documentation.
Linking to Zeta for a base game¶
When compiling and running your project, make com.thirds.zeta.desktop.DesktopLauncher
the main class, and put the Zeta binary in your game’s classpath. You should now be able to test Zeta. You don’t need to write any code at all at this point, Java should automatically invoke Zeta’s initialisation methods.
Linking to Zeta with mods¶
Zeta detects all mods in its classpath, so if you want to allow support for end-user mods as well as your game mod, make sure to run your game with a command like java -cp * com.thirds.zeta.desktop.DesktopLauncher
rather than referencing a jar directly. End users can then place their own mods in the folder you added to the classpath.
Modding¶
Zeta’s engine is designed around the core functionality of mods. Anything you make on top of Zeta is a mod; even a game is just a mod running on Zeta. This modularity allows for greater flexibility in the engine’s internal workings. Please read the setup page first - make sure you can run the Zeta engine before trying to mod it.
Creating a mod¶
To create a mod for Zeta, simply create a class anywhere which implements com.thirds.zeta.mod.Mod
. When Zeta
initialises, it will search for all mods in its classpath and load them. You need to implement the methods getName()
and getVersion()
. The mod name should be written entirely in ASCII lower case, with digits and hyphens. Version
number is arbitrary, but will be used for dependency management in the future.
Entities¶
In the Zeta engine, an entity is any object in a level. This can be as simple as a collision brush or a point light, or as complicated as a player character or an AI-driven NPC. All entities must follow the same framework:
- They all inherit from
com.thirds.zeta.entity.Entity
- All non-abstract entity classes must have only a single, public constructor, which takes an EntityData object. This data is the position, life, physics information and entity ID loaded from level files and save files.
Creating entities¶
In a level file¶
Coming soon - the level editor isn’t implemented yet!
Programmatically¶
Sometimes, you will want to create entities on the fly during a level. Never call the entity’s constructor directly since it won’t get registered properly into the level; instead, use the EntityFactory. Provide it a class and some entity data and it will handle calling relevant constructors and registering the entity into the level for you.
The entity will instantly become available for you to call methods on, but its update/render methods won’t fire until the next frame. This ensures that each entity always gets one complete frame’s worth of updates at a time - if update was called, you know that preUpdate was also called just before.
Never reuse the same EntityData for initialising multiple entities! This will mess with the ID system.
EntityData in detail¶
Here is a detailed description of the EntityData class. You can get any entity’s EntityData by calling getData.
Coordinates¶
Entities use a 3-dimensional vector to store position data. The first two components comprise the x- and y-coordinates of the entity. The bottom left of the screen is (0, 0). The z-coordinate is used to control which entities get updated and rendered before which others, but you shouldn’t need to worry about it in most cases. This is discussed in more detail below.
Size¶
An entity’s size expands upwards and to the right (fitting with the coordinate system). Size has little in-built usage in Zeta apart from with physics and collision detection. The method getCentre finds the coordinates of the centre of the entity by using the size.
Physics¶
Entities have no collision detection by default. By setting the physics object, you can enable this functionality. You should ensure that any physics object you create matches the size of the entity. By calling setPhysicsObjectToAABB, Zeta can create a rectangular physics object that matches the size of the object. If a physics object is solid, players can’t walk through them. Otherwise it is only used for custom collision detection, including triggers.
Life¶
By calling getLife, it tells you how many seconds the entity has been alive (in the level) for.
Name¶
You can give entities a custom name when in the level editor. This has no intrinsic functionality, apart from being useful when trying to find specific entities programmatically.
Event methods and invocation order¶
Event methods¶
Entities are event-driven. Once they are created, they will have a number of methods automatically called by the level that they’re in.
- preInit, init, postInit
- Called as soon as the entity is created. Always use these methods instead of constructors to initialise data, because constructors can be quite volatile when dealing with save files (and they’re harder to work with when modding), so Zeta provides this useful alternative.
- preUpdate, update, postUpdate
- Called once every frame while the game is not paused. Takes in a single argument, delta, which is the time in seconds since the last frame.
- preRender, render, postRender
- Called once every frame whether or not the game is paused. Takes in a single argument, delta, which is the time in seconds since the last frame. Uses the level’s coordinate system, drawing below all UI objects.
- preRenderUI, renderUI, postRenderUI
- Called once every frame whether or not the game is paused. Takes in a single argument, delta, which is the time in seconds since the last frame. Uses the screen coordinate system, drawing above all game objects.
- dispose
- Called when the entity is removed from the level and when you might need to clean up data or child entities.
- resize
- Called when the window changes size.
For detailed descriptions on how to use the render methods, look at LibGDX’s documentation on how to use the SpriteBatch, since Zeta uses their libraries for rendering. Don’t use your own sprite batch, just use the one provided in the render methods! If tinting the sprite batch, reset it to white after you’re done with rendering your entity.
Event rules¶
As a rule of thumb, put game mechanics in the update method family and put purely graphical mechanics in the render method family. Try not to mix-and-match; weird things may happen when the game is paused or running on a slow machine!
Invocation order¶
Every frame, the event based methods are called on every entity (as in, all preUpdates happen before any updates or postUpdates) in the following order:
- preUpdate (and updateMovement if applicable)
- update
- postUpdate
- preRender
- render
- postRender
Highest first z-order¶
The order in which entities get updated within this list is determined by their z-order, more specifically a highest first order. For example, an entity with a z-coordinate of 100 will be rendered before one with a z-coordinate of -250. When changing the position of an entity, Zeta automatically adjusts its z-coordinate to match its y-coordinate (you can do this manually using the setZFromY method). This means that entities are updated and rendered from the top of the screen downwards, ensuring that entities at the bottom of the screen correctly visually overlay entities that should appear behind them.
Other methods¶
Die¶
By calling this method, the entity is removed from the game at the start of the next frame, and its dispose method is called. You can also call LevelScreen.getInstance().getLevel().removeEntity(ent).
Save files¶
Zeta’s save file system serialises all entities, and allows you to choose which fields to serialise and which to freshly
initialise yourself when loading from a save file. By marking a field as transient (e.g. private transient int
variableName;
), this tells Zeta not to save it into the save file. This allows the save file to be smaller and faster
to load. Here is a list of object types you may want to make transient:
- Graphics objects, because they are easy to initialise in your init method, for example:
- AtlasRegions
- Textures
- Other asset files like sounds and music, for the same reason.
- Other entities as fields
Do not reference another entity as a field in your entity (TODO warn/error when this occurs). When loading back from a save file, it won’t load into the level properly and may even create two copies of the entity; one from the level itself and one from the field. In the worst case, two entities reference each other and produce an infinite loop trying to save all the data! Instead, always use EntityRefs to reference other entities.
Calling the method ref on an entity gives you a reference that can be queried to retrieve the original entity, and can also be saved as a field. This EntityRef object has a method, get, which returns the original entity that was referenced. Another (perhaps more useful) method is refs, or “reference specific”. This gives you an EntityRef.Specific which enforces that the entity it references is of the correct class.
Bear in mind that if you want to put an Entity in a transient field, that is fine because transient fields don’t get saved.
Queued actions¶
QueuedActions are like alarms that fire events at some specified time in the future, during one (or more) of the entity’s update and render methods. To create a queued action, always use a concrete class not a lambda, since save files can’t reconstruct anonymous classes. Use the queue method to install it into the entity. The life parameter in the QueuedAction constructor shows how many seconds it should wait for before executing the action.
Levels¶
In Zeta, a level contains and manages every entity, and levels themselves are handled by a LevelScreen.
How to access the level objects¶
Level functionality is split between several classes: the Level, the LevelRenderer and the LevelPhysics. They all have accessors in the LevelScreen class. For example, to get the level renderer, write LevelScreen.getInstance().getLevelRenderer(). You can hook the constructors of both Level and LevelRenderer.
Level object¶
User functions¶
Here are some useful methods in the level object.
- find
- Allows you to find entities that match a class or superclass, and optionally also a name.
- entityExists
- Allows you to tell whether an entity is currently in the level. If not, that entity won’t be saved properly.
Internal functions¶
These functions are called internally and shouldn’t normally be used. Alternatives are listed.
- addEntity
- Use EntityFactory.generate.
- removeEntity
- Use Entity.die.
- reorder
- Use Entity.setPos to let Zeta reorder the entity within the z-order.
- update
- Called automatically every frame by Zeta internally.
Level renderer object¶
User functions¶
- unproject
- Allows you to project UI coordinates (where (0, 0) is the bottom left, and the window dimensions dictate the size) into in-game coordinates (which is, for example, the coordinate system entities are stored in).
- getMouseCoords
- Returns the UI coordinates of the mouse.
- getUnprojectedMouseCoords
- Allows you to find the location of the mouse pointer in game coordinates.
Internal functions¶
- render
- Called automatically every frame by Zeta internally.
- resize
- Called automatically whenever the window changes size by Zeta internally.
- dispose
- Called automatically when the level is exiting by Zeta internally.
Level physics object¶
User functions¶
- collideAny(Solid)
- Returns the first entity whose physics object intersected the provided entity’s physics object. If the solid variant is called, only solid physics objects count. It returns null if no object was hit.
- collideAll(Solid)
- Returns the list of all entities whose physics objects intersected the provided entity’s physics object. It returns an empty list if no object was hit.
- collideAnyCharacter
- Returns the first GameCharacter whose physics object intersected the provided entity’s physics object. It returns null if no object was hit.
Hooks¶
Hooks allow mods to inject their own code into some of Zeta’s functions.
Example¶
For example, Zeta provides a hook into the constructor of the LevelRenderer
class. In order to hook into that
constructor, you need to use the HookManager
object in that class, called LevelRenderer.constructorHM
. It has
two methods that you can use, addPreHook
and addPostHook
. If you add a hook using addPreHook
, your code will
be executed just before the default behaviour in the constructor. If you instead use addPostHook
, your code will be
executed just after the object initialises. Here is an example of using this hook to change the background colour of the
render engine.
LevelRenderer.constructorHM.addPostHook(args -> {
LevelRenderer levelRenderer = args.getA1();
Level level = args.getA2();
levelRenderer.setGlClearColour(Color.MAGENTA);
return null;
});
Add the above code to your mod’s init method to try it out. Note that the arguments to the function (including the ‘this’ pointer to the LevelRenderer) are encapsulated inside an args variable.