Friday, 14 April 2017

Urho3D and I76 Levels

Urho3D and AngelScript

What's Urho3D?

Urho3d is a game engine - it provides a simple method of implementing a:

free lightweight, cross-platform 2D and 3D game engine
The complete Urho3D release is available at the Urho3D homepage on GitHub, and describes the list of features provided by the engine.

What's AngelScript?

AngelScript is the main scripting language of Urho3D, and Urho3D ships with a slightly modified version in the source tree for running script applications.

Although AngelScript has many of the typical features of scripting languages (such as higher level data types, type inference, automatic memory management, etc) it supports a "C++ like" syntax and has a low overhead when binding to the native API.

The bin/Data/Scripts/ directory in the Urho3D distribution contains a set of example files which demonstrate the basic features of the engine, alongside a level editor and sample game constructed in AngelScript. The distribution also includes an Urho3DPlayer application, which can be used to run scripts directly.

Urho3D also supports Lua as an alternative scripting language, but AngelScript is the default and I'll stick with that.

And where are we going?

The idea is to use AngelScript and Urho3D together with the file format information we have on I76 to decode and provide a realtime fly through view of some of the I76 level geometry.

Over the next few posts I'll go over the basics of running up Urho3D - getting things running, the basic application structure, etc. Then the main flythrough application.

Getting Started

Urho3d Absolute Basics

A few basic concepts to keep in mind are:

  • Urho3D provides a set of subsystems for use in game code
  • Scripts access most of these subsystems via global references
    • e.g.: files, logging, network, input, ui, audio, engine, graphics, rendering, scripting, console, scene, octree, physics, resource cache, and time
  • The Urho3D Engine handles the main running loop for the script, and provides events for the script to react to
  • Scripts include each other directly
  • Many user created items are built around "components" and "nodes"
  • Components mainly provide functional game elements
  • Nodes track 3D properties, and contain components or other nodes
  • Nodes are placed in a graph hierarchy
    • This means that nodes can be attached as children to other nodes
    • The base of the Urho3D application is a root scene node
    • Things like lights, cameras, and 3D objects are created as children of this node
  • But not everything is a node
    • e.g. ui elements are not nodes, but have a similar parent/child relationship to each other
  • Urho3D provides an event driven framework where:
    • The script reacts to global events with a set of function hooks (e.g. Start() and Stop())
    • Scripts can subscribe listener functions to events (e.g. "KeyDown" or "Update)
    • Scripts can also generate events
The complete list of scripting objects and types are in the file AngelScriptAPI.h. The events available are described in various header files and can be found with the URHO3D_EVENT macro.

Urho3D has enough minor differences from the default AngelScript conventions that the primary AngelScript documentation is useful, but not comprehensive. The basic rule here seems to be to search for a usage in the example scripts to determine the canonical way of using the scripting language.

The simplest example

This is a basic graphical hello world - it's simpler than the sample example, and uses the minimal possible code. Just save this into a file HelloWorld.as:

void Start() {
    Text@ txt =  Text();
    txt.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 30);

    txt.text = "Hello World, It's " + String(time.systemTime);

    ui.root.AddChild(txt);
    SubscribeToEvent("KeyDown", "HandleClicked");
}

void HandleClicked(StringHash eventType, VariantMap& eventData) {
    engine.Exit();
}

How does that work then?

You run the example with the Urho3DPlayer application: try a line like

Urho3DPlayer HelloWorld.as
or in a window mode with
Urho3DPlayer HelloWorld.as -w
Either press a key (or close the window if in windowed mode) to exit.

Details

The player application loads a script file from the command line, and executes the contents.

The primary entry point of the script is the "Start()" function. This is executed by the Urho3D engine. As the official docs say this is called at engine initialization and before running the main loop.

The first line here starts "Text@ txt" which defines an object handle. The "@" designation is used by AngelScript for reference counted object handles. In this case a Text object, the default Urho3D UI text type.

Given a txt object reference we can use a C++ style member reference through a dot operator to access properties and methods. Here we set the font face and size using txt.SetFont().
In this case the font loaded is taken through the global resources cache, which loads from the installed Urho3D directory, under bin/Data/, and uses the global cache reference.
So cache.GetResource("Font", "Fonts/Anonymous Pro.ttf") call is loading the TTF Font from the bin/Data/Fonts/ directory.

Next we set the "text" property, which is the string to render. The time.systemTime() call gives us the current time.

Both cache and time are two examples of the subsystems services provided by Urho3D on initialisation, and available for the scripts to access through global references.

As mentioned Urho3D uses a parent-child set of relationships, and the AddChild() call places the text entry as a child of the root of the ui. (Although you should be aware that these are UI elements, and not Nodes).

Finally SubscribeToEvent() is used to link the events from Key input ("KeyDown") to the function "HandleClicked()". Note that in scripts we use the name of events, which are converted to the identifiers that the C++ code uses, however these are case insensitive at the script layer, so subscribing to "KeyDown", "keydown" or KeYdoWn" will all do the same thing.
For this app then the HandleClicked() method simply calls an Exit(). This exits the entire Urho process and leave us back at the command line.

A Slightly More Verbose Version

This example is slightly more verbose, and resembles the default sample slightly more. It:

  • Uses input.mouseVisible to ensure the mouse remains visible
  • Has a different event handler for the first frame update, then switches for subsequent frames
    • Where the Update event is sent each frame
  • Has a handler called on every update which will drop out a flood of console debug
  • The text handle is a file global and on each keypress we:
    • Exit if the Escape Key is pressed, otherwise
    • update the text colour and position (only seen on the first keypress)
    • change the on screen text as key presses occur

One interesting feature of this version is that we can pull away focus from the main window, and should see the time step report change, since Urho3D will deliberately lower the frame rate when the Application loses focus. The engine SetMaxInactiveFps() can be changed to modify this.

Text@ instext;

void Start()
{
    Font@ font = cache.GetResource("Font", "Fonts/Anonymous Pro.ttf");
    instext = Text();
    instext.text = "Hello World, It's " + String(time.systemTime);
    instext.SetFont(font , 30);

    ui.root.AddChild(instext);

    input.mouseVisible = true;

    SubscribeToEvent("KeyDown", "HandleClicked");
    SubscribeToEvent("Update", "FirstFrame");
}

void FirstFrame(StringHash eventType, VariantMap& eventData)
{
    instext.text += "\nFirst Frame at " + String(time.systemTime);

    SubscribeToEvent("Update", "HandleUpdate");
}

void HandleUpdate(StringHash eventType, VariantMap& eventData)
{
    Print("HandleUpdate Called  at " + String(time.systemTime) + "\n");
    float timeStep = eventData["TimeStep"].GetFloat();
    Print("  Time Step " + String(timeStep) + "\n");

}

void HandleClicked(StringHash eventType, VariantMap& eventData)
{
    if (eventData["Key"].GetInt() == KEY_ESCAPE)
    {
        engine.Exit();
    }
    else
    {
        instext.text += "\n Keypress at " + String(time.systemTime);
        instext.horizontalAlignment = HA_CENTER;
        instext.verticalAlignment = VA_CENTER;
        instext.color = Color(0.0f, 1.0f, 0.0f, 1.0f);
    }
}

And that's all for now - next time we'll cover a simple 3D scene setup, nodes and generate a simple 16-bit heightmap...