Introduction

Welcome to the Amateur 3D Game Engine.

Before to start, be sure you have read the README.md file.

  1. Get started
    1. The Application object
    2. States
      1. State initialization and finalization
      2. State update
    3. Rendering pipe
      1. The simple way
      2. Rendering the HUD
      3. Fine control
  2. Loading Data
    1. Loading models
    2. Loading maps or scenes
  3. Collisions
    1. Basics
    2. Using the onCollision event
    3. Check object collision
    4. Check volume collision

Get started

The Application object

A3DGE defines an Application object. This object contains some common components such as the display, the configuration file or the keyboard handler. It also controls the initialization and the application execution.

You can use the default TA3DGEApplication class or extend it to add more components or add more functionality.

The application object also uses game states to control the execution. These game states are similar than forms on Delphi and Lazarus.

The minimal program file may look like this:

program MyGame
uses
  a3dge, Game;
begin
  a3dge.Application := TA3DGEApplication.Create (Nil);
  a3dge.Application.Initialize;
  a3dge.Application.AddGameState (Game.TGameState.Create);
  a3dge.Application.Run
end.

States

A3DGE uses game states to control the game flow. The idea is similar than Delphi and Lazarus TForm classes: extend TGameState implementing its Update and Render methods, create an object and assign it the game's state manager, and finally call the Run method.

In most games you'll have to create more than one state. For example, most games have an introduction or title sequence, a configuration dialog, the gameplay and a game over screen. Each of these can be implemented as different states and move from one to other using Application.GameState.Current property.

The example ˜/src/examples/ex_gameflow.pp shows how to extend TGameState to create the different states, how to add them to the a3dge.Application object and how to change game state when needed.

Note that TGameState extends TState so it inherits most of its functionality.

State initialization and finalization

To initialize the game state, use its Enter method; it will be called just before the state becames the Current one.

If you decide to load (or create) data in the Enter method you must know that the game won't dispatch events until it exits this method. So when the method ends, A3DGE will dispatch all events queued. That may include update events (game ticks) that will call Update several times, maybe a lot of times depending how much time it needed to load data.

There are ways to deal with this. The most direct and aggressive (yet effective) one is this one:

(* Initialize the state, loading all data. *)
  procedure TMyGamePlayState.Enter (aEntity: TObject);
  var
    lApp: TA3DGEApplication;
  begin
    inherited Enter (aEntity); { Play nice with the engine. }
    lApp := aEntity as TA3DGEApplication;
    try
    { Stop event handling. }
      lApp.PauseUpdates;

    {
      Put here the code to load your data.
    }

    finally
    { Clear and restore the event handling. }
      lApp.ClearEventQueue;
      lApp.ResumeUpdates
    end
  end;

You may use a similar code in the Leave method too as releasing resources may be also time consuming.

See also:

TState.Enter TState.Update TGameState.Render TState.Leave

TA3DGEApplication.GameState

State update

Calling a3dge.Application.Run will start a loop that will call the current state's Update method in a regular pace. This pace is defined by the TA3DGEApplication.TicksPerSecond property. Default value is a3dge.FPS. Run will also call the current state's Render method when it's possible.

Rendering pipe

The rendering pipe is the sequence of commands that allows to render a scene. On A3DGE this is done in the Render method of the scene object.

The simple way

Let's start with a simple state that just renders a 3D scene and nothing more.

To render a scene you have to set the OpenGL context in a perspective projection. If this won't change it can be done in the Enter method. Then, in the Render method, just call the method from the World3D object. For example:

procedure TMyGamestate.Enter (aSender: TObject);
begin
  a3dge.Application.World3D.Camera := TCamera.Create;
  a3dge.Application.World3D.Camera.SetViewport (
    0, 0,
    a3dge.Application.Display.Width, a3dge.Application.Display.Height
  );

  Add here the scene objects...

  a3dge.Application.World3D.Camera.ApplyViewport;
  a3dge.Application.World3D.Camera.SetPerspectiveProjection;
  a3dge.Application.World3D.SetOpenGLContext
end;



procedure TMyGamestate.Render;
begin
  a3dge.Application.World3D.Render
end;

Rendering the HUD

Render the HUD is a bit more complex. The simplest way is to draw it using Allegro. The problem is that Allegro changes some of the OpenGL context states so you'll need to restore them in order to render the scene.

procedure TMyGamestate.Render;
begin
{ Render the 3D scene. }
  a3dge.Application.World3D.Camera.SetPerspectiveProjection;
  a3dge.Application.World3D.SetOpenGLContext;
  a3dge.Application.World3D.Render
{ Render the 2D HUD. }
  a3dge.Application.World3D.Camera.SetOrthographicProjection;
  a3dge.Application.World3D.DisableOpenGLContext;

  Put here the code to render the HUD...

end;

Remember that Allegro drawing routines may be affected by the OpenGL context, hence the call to DisableOpenGLContext. Also you don't need to call al_flip_display as this is managed by the Application object.

Fine control

Despite TWorld3D is a wonderful class that deals with everything needed to render a scene, it is big, needs a bunch of memory and its initialization is a bit slow.

Sometimes you just need to render one or two models, for example in the player personalization screen where you just render the selected model and the stats. In that cases use the TWorld3D class is possible but not needed.

If you want to render just a simple model then you have to create your own TCamera object and deal with some of the OpenGL context by yourself but it is still simple.

procedure TMyGamestate.Render;
begin
{ Set up the OpenGL context.  It doesn't enable lights nor fog. }
  fCamera.SetPerspectiveProjection;
  glFrontFace (GL_CCW);
  glCullFace (GL_BACK);
  glEnable (GL_CULL_FACE);
  glDepthFunc (GL_LEQUAL);
  glEnable (GL_DEPTH_TEST);
  glEnable (GL_TEXTURE_2D)
{ Render the model (using a TObject3D as a container). }
  fObject.Render
{ Render the 2D HUD. }
  fCamera.SetOrthographicProjection;
  glDisable (GL_CULL_FACE);
  glDisable (GL_DEPTH_TEST);
  glDisable (GL_TEXTURE_2D)

  Put here the code to render the HUD...

end;

See also:

a3dge.TA3DGEApplication.World3D TWorld3D TCamera

Loading Data

Loading models

This version includes functions to load Wavefront .obj and PLG files. PLG is a very old yet simple format so you would like .obj files instead.

Before to load a model you should register the loader. For both .obj and PLG file it's done by just including a3dge.Wavefront and/or a3dge.PLG units in any USES clause. Then you can use a3dge.Model3D.LoadModel to load your models.

Loading maps or scenes

This version doesn't have a way to load maps or scenes but there are plans to add it in future versions (roadmap says in version 1.2 so be patient).

BTW you can use 3rd party editors, such as TILED or Mingro ones. They're simple to use and include description of the file formats so you can write the loader by your own.

Collisions

Basics

There are different ways to check collisions, depending on the needs.

Before to check collisions you should assign the object's bounding volume. There are two:

The bounding box is optional but the sphere is mandatory: if an object doesn't have a bounding sphere or its radius is zero or negative then it will be ignored by the collision checkings.

The bounding volumes arent assigned automatically but you can use methods TModel3D.BoundingSphereRadius and TModel3D.BoundingBox.

See also: TObject3D.UseBoundingBox

Using the onCollision event

This will be familiar to Delphi and Lazarus users.

The idea is let the engine to check the collisions by itself and notify the objects that collide using the TObject3D.OnCollision property. This event receibes a reference to the other object involved in the collision. Note that if both objects have an OnCollision event assigned, then both methods will be called.

A3DGE doesn't check for collisions automatically: you must call TWorld3D.CheckCollisions.

Check object collision

You can check the collision between two specific objects by using the TObject3D.CheckCollision. For example:

if fPlayerObject.CheckCollision (fWallObject) then
  Self.Stop;

Use this if you only need to check a bunch of objects. If you have several docens then TWorld3D.CheckCollisions method is quite more efficent.

Check volume collision

unit a3dge.Collisions have a collection of functions that calculate collisions between different kind of volumes. These funcions are used internaly but some times you may need them.

Also class TWorld3D have a few methods that check collisions between a volume and the contained objects, as TWorld3D.CheckSphereCollision. These methods will return True or False depending if the volume has collided with any object or it doesn't. These methods also allows to filter the objects by using the Id or Tag property values.


Generated by PasDoc 0.15.0. Generated on 2025-07-31 11:41:01.