Although the examples in this article are given in a C like syntax, they probably won't work out of the box. They're more for inspiration than actual working code.
This article explains an alternative approach to the game loop found in most game engines. A typical loop might look something like the following:
do { handle_input(); update_animations(); update_entities(); draw_everything(); } while (!finished);
There's nothing wrong with this approach, but it can become unwieldy as time goes by and more things are added. What if you want to make the game support online multiplayer at some point? What about adding new features, such as a debug console? How about screen management, or changing how game objects are rendered?
Kernel-based Engine
The kernel-based game engine is designed to take care of these problems. It does add some overhead to the main loop, but it's still fast enough for most games out there, and the added flexibility really helps further down the line.
This model splits things into two sections: the Kernel
object, and a
collection of Service
objects.
The Kernel
takes care of running the loop, and dispatches messages to
Service
objects.
Each Service
object takes care of a single piece of functionality. This can
include updating game entities, managing game screens or handling input.
Keeping the functionality of services limited helps to make things reusable as
time goes on, and prevents things becoming too complex.
Our loop now looks something like this:
do { kernel.update(); kernel.render(); } while (!kernel.isFinished());
Note: The "render" method is optional, but makes things much easier once different update & render priorities come into play. For example, a service may need to render last but be updated before other services update (see Render Method for a more detailed explanation).
The Kernel
A very basic Kernel
class may look something like this:
class Kernel { private ServiceCollection _services; private Map<Class, Service> _serviceLookup; public void update(); public void render(); public void start(); public void stop(); public bool isFinished(); public void addService(Service service, Class serviceType); public void removeService(Class serviceType); public void getService(Class serviceType); }
When a service is added, its class type is also stored. In most cases this will be the same type as the service, but it can be used to extend a service class and then override it. This makes it easier when referencing a service elsewhere.
Other points:
- The
_services
field is for iterating over services during update & render loops. _serviceLookup
is for looking up services in theadd
,remove
andget
methods.update
andrender
are called every loop.
The Game Service
class GameService { private int _priority; private int _updatePriority; private int _renderPriority; public void render(); public void update(); public void stop(); public void start(); public void onSuspend(); public void onResume(); }
Not every service needs to render or update, so there should be a way of making these methods optional.
An example
Below is the updated version of the game loop from the beginning of the article.
// Create kernel Kernel kernel = new Kernel(); // Add services kernel.addService(new InputService) kernel.addService(new UpdateEntitiesService) kernel.addService(new RenderingService) // Start kernel & run loop kernel.start(); do { kernel.update() kernel.render() } while (!kernel.isFinished());
There's not a huge difference, but hopefully you can see the advantages. In
theory the same parts of the game could be reused in a server app by skipping
the render calls and removing RenderingService
.
Important Points
Render Method
The kernel doesn't necessarily need a render method, but it makes things easier to read and rendering is a pretty big part of most games.
Some things also render in a different order to being updated - for example, a debug console must be updated first so that it can reset keyboard events (to stop them being used in other services), but it must be rendered last so it appears on top of everything.
Overhead
Using this approach adds a small amount of overhead. If you desperately need every ounce of performance or memory, then the traditional method works just fine.
Reuse
By splitting things into separate services, it becomes almost trivial to use the same components for different games.
Flexibility
This is perhaps the biggest selling point. Games that require a central server game can remove unnecessary services whilst still reusing core components.
Services can also be enabled & disabled at runtime, so a debug console can be enabled for different builds, and can services can be switched out during program execution (via the debug console).
Example Services
Here are a few services from games under development:
ScreenManagerService
Handles GameScreen objects. This acts as a form of state management, and screens can be pushed onto a stack for menus and overlays.
DebugConsoleService
Handles the debug console. Lets a user enter commands and query things. This
service usually has access to the kernel
and can request other service
objects.
DebugListenerService
This service listens for changes on a specific directory, and fires an event
when a file is changed. The ResourceService
listens for these events, and can
reload assets when something is changed. This is particularly useful when
tweaking graphics, as the engine can be running in one windows whilst things are
being updated.
ResourcesService
Holds references to resources (images, sounds, animations etc) that can then be accessed inside other parts of the engine.
InputService
Handles user input, either from a game pad or the keyboard.
EventsService
Manages a simple event dispatcher.
ScriptEngineService
Acts as a common execution environment for scriptable objects.
SessionService
Manages gameplay sessions and handles save states.
RendererService
Handles 2D rendering.
SoundService
A single point for playing and sounds.
AmbienceService
Used to manage ambient effects in a game, such as weather or time of day effects. Although this can be done separately via several separate services, this wraps things up in a nicer package.
GameEntityService
Manages component based game entities (which is another large topic).