Alegria Engine

Contents

 

Information

Overview: Alegria is an open-source 2D Game Engine for Windows. User can create games through its visual editor and Python scripting.

Source code: Link to Google Code

Download:  Link to download page

Languages and tools used:

  • Written in C++ using Visual Studio as IDE and compiler.
  • OpenGL for rendering.
  • OpenAL for sound.
  • Box2D for physics.
  • Embedded Python for scripting.
  • TinyXML for XML parsing.
  • SOIL for texture loading.

Images:

engine1 engine2 engine3

Video:

 

Related projects:

 

Features

  • 2D hardware-accelerated rendering.
  • Python scripting.
  • Entity-Component architecture.
  • Physics and collision detection (via Box2D).
  • Sound (via OpenAL).
  • Bitmap text support.
  • Particle systems.


 

 

Technical details and code samples

Entity-component architecture

Alegria Engine uses a component driven architecture. Entities are treated as component containers. Therefore, an entity is defined by its components and the data they hold.  This allows new functionality to be added easily to the engine, in the form of new components.

For reasons beyond the scope of this text, AK_Entity is a subclass of AK_ComponentContainer which holds the component management code.

Relevant code: AK_Entity.hppAK_ComponentContainer.hpp

All components must be a subclass of AK_Component which acts as an interface that must be implemented as well as implementing some component-generic code (ownership. state variables, etc).

Relevant code: AK_Component.hpp

The engine itself provides with a set of components wich provide the user with a decent base functionality, such as: Render, Physics, Sound, Keyboard, etc.

 

Subsystems

The subsystems work with the data held by the components. They are updated each frame and they perform operations on the components. Examples of subsystems are the Render subsystem (which use the Render component data to make the proper OpenGL calls) and the Physics subsystem (which updates the Box2D world), among others. Usually each type of component has it’s own subsystem.

In a similar fashion as components, subsystems must be a subclass of AK_Subsystem.

Relevant code: AK_Subsystem.hpp

ClassDiagram3

Basic inheritance diagram for entities, components and subsystems.

 

Events system

Game logic in Alegria follows a very simple cause – consequence scheme, which consist of three elements:

  1. Conditions: Events the entity looks for, like a specific collision, a key pressed, etc.
  2. Triggers: Groups of conditions. If at a certain moment all conditions of a trigger are met, it activates, executing its script.
  3. Script: Python script that will be called when a trigger activates.

In a logic sense, a trigger works as an AND operator for all the conditions it has. An OR operator can be emulated by using two (or more) triggers with the same script attached.

Note that this scheme applies at an entity-level scale. I.e: it’s an specific entity who holds these elements, and who they belong to.

Relevant code: AK_Condition.hpp,  AK_Trigger.hpp

Each type of component can haver zero or one conditions. So while the Keyboard component has the AC_Keyboard condition (it is met when an specific key is pressed), the Render component has none.

All conditions can be inverted which has the same effect as applying a NOT operator (v.g. when a key is NOT pressed, when an entity is NOT colliding, etc.).

Also their period can be modified (omega parameter). This allows the user to specify the minimum logic ticks that need to pass before the condition can be activated again. This has many uses, for example the user may want the player to shot at an specific rate, like every 100 ticks, so he would have to the Keyboard condition’s omega to 100. An omega of 1 will be able to be activated every frame, while an omega of 0 means that this condition can only be activated once on its lifetime (useful for calling initial scripts which only need to be executed once).

 

Python embedding

Despite the engine being written in C++, the game logic is defined through Python scripts. There is a layer wich abstracts the low-level C++ programming and provides a Python API to the user, which has many benefits. For instance, Python scripting is easier to pick up than C++ for a novice programmer and it arguably gets the job done quicker (not performance-wise, of course). Also, this allows the user to modify the game logic without having to recompile, which provides quick prototiping capabilites.

One of the biggest challenges was to expose my C++ objects (entities, components…) to the Python environment. To do this “proxy” objects are defined. Those proxy objects are lightweight Python-compatible objects which hold a pointer to the actual C++ objects. For example, here’s a proxy object of an entity:

struct APY_EntityProxy{
   PyObject_HEAD
   AK_Entity *ref;
};

This is the object that will be exposed to the user when he receives an entity from a Python function. Calls will be made to this object’s methods, which will in turn call the actual object’s methods. An example of such method is:

PyObject* _apy_SetRotation(PyObject *self, PyObject *args, PyObject *kwds){
   float x;
   PyArg_ParseTuple(args, "f", &x); // We parse the Python arguments into a C++ float variable.
   ((APY_EntityProxy*)self)->ref->SetRotation(x); // We call the actual object's method
   Py_RETURN_NONE;
}

Relevant code: AK_EntityProxy.hpp

To boost performance, all scripts are compiled into bytecode when loaded.

 

Scripting through the Python API

The user has two ways to connect with the current state of the game when scripting. The first one is through the “owner” variable, which is avaiable in all the script calls. This variable represents the entity who called the script, and the user can interact directly with it or its components. Example:

x,y = owner.GetPosition()
owner.SetPosition(x+0.5, y*2)
renderComp = owner.GetComponent("Render")
renderComp.SetVisible(false)

Some methods will give access to other entities (and, therefore, its components). For example, you can get all the entities the caller collided with:

## Script that deletes all the entities this object collides with
physicsComp = owner.GetComponent("Physics")
colEnts = physicsComp.GetColEntities()
for ent in colEnts:
   ent.Kill()

The second way is through the Alegria module, which lets the user interact with the engine itself by, for example, creating new entities, getting entities by its name, loading a new scene, etc. Example:

import Alegria
enemy = Alegria.CreateEntity("Enemy2", 3, 2.5, 0, 1) # Archtype name, posx, posy, rot, size
Alegria.Quit() # The game ends.

This lets the user to define global variables which can be accessed from different script calls:

import Alegria
Alegria.score = 20 # From now on, this variable can be accessed from every script call

Relevant code: AK_InterfaceProxy.hpp , AK_ScriptInterpreter.hppAK_ScriptInterpreter.cpp