Warlock Engine
Project Status Ongoing
Project Type Unknown Type
Project Duration Unknown Duration
Software Used Unknown Sofware
Languages Used Unknown Languages
Primary Role(s) n/a

Warlock Engine is an ongoing project that I've been working on since 2016.

The idea behind Warlock was to create my own personal "toolbelt" for my personal projects. I work on different projects in my spare time, and wanted a framework in which I felt completely comfortable and had all tools I need to my disposal so that I can quickly iterate and prototype new ideas.

Warlock consists of a few "big" systems and many "smaller" low level systems. Since the big systems are the most interesting/visual to talk about, I will cover them first.

Level Editor

The Level Editor is where all game logic comes together! Warlock incorporates an advanced level editor with a more "traditional style" entity component system.

Hierarchy

The level editor supports an advanced hierarchy system with a "virtual hierarchy" as the main defining structure of a level, and a "node hierarchy" to provide the physical structure. This allows things like folders in the virtual hierarchy while not cluttering the node hierarchy.

leveledit_hierarchy.png#asset:523

Live Tweaking

Debug drawing is possible with both 3D shapes, meshes and ImGui. You can also tweak object properties live while playing.

You can add as many viewports as you'd like, and each viewport can define its own debug drawing. This was one of the most common limitations I've found in different game engines and I wanted to make sure I did this right. You can also have multiple level editors open simultaneously.

leveledit_tweak.png#asset:520

Templates

Templates are an important aspect of building level. They are essentially what "prefabs" are in Unity, and "blueprints" in Unreal Engine.

template_editor.png#asset:522

Templates can be instanced in the world, and a new set of UIDs is generated and mapped to the base instance.

This way, you can link to entity components even if they are template instances.

leveledit_componentselect.png#asset:524

Sockets

Entity Components can be parented to a bone or multiple bones (with influences). This is still a bit primitive (would be nicer with a bone name selector).

sockets.png#asset:521

Node Graph

erg

Sequences

The Sequence Editor incorporates an advanced sequencing system that is flexible and easily expandable. It supports a weak resource binding system that allows sequences to be reused in different contexts, such as them being part of instanced templates.

Timeline

dplib_2x6QqtwoIF.png#asset:511

The Timeline uses my robust Canvas system that supports many quality of life features such as zoom-to-cursor, integrated timeline markers for multiple axes, custom drawing of keyframes, "clips" that have a begin and end time on the timeline, and more. On the left hand side is a hierarchy view with preview values that update in realtime and can be modified to insert new keyframes.

A sequence in Warlock Engine is nothing more than a hierarchy of items, some items can be tracks, some of them are simply hierarchical elements. 

Channels

Sequences support many predefined types of curves. These can be single value float curves, or compound channels for vectors. It also supports "data" channels that support templating to provide the value type of the keyframe. One common example of data channels is to store UIDs for a certain resource, like camera shots.

struct SeqCameraDirectorKey
{
	uint32 mShotUID = 0;

	void Pack(C_DataPack& aPack) const { aPack.Set("shotUid", mShotUID); }
	void Unpack(const C_DataPack& aPack) { aPack.Get("shotUid", mShotUID); }

	bool operator==(const QDSeqCameraDirectorKey& aOther)
	{
		return mShotUID == aOther.mShotUID;
	}
};

class SeqCameraDirectorItem : public SEQ_SequenceChannelData< SeqCameraDirectorKey >
{
	typedef SEQ_SequenceChannelData< SeqCameraDirectorKey > Super;
	WAR_DECLARE_OBJECT_INHERIT("SeqCameraDirectorItem", SeqCameraDirectorItem, Super);

	static void ObjectRegisterTypeInfo(SEQ_ItemTypeInfo& info);

private:
	SEQ_SequenceItemInstance* CreateInstance() const override { return new SeqCameraDirectorInstance(); }

	C_Color GetEditorColor() const override { return C_Colors::OrangeRed; }
	bool OnEditorHierarchy(float aPlaybackTime) override;
};

The result:

camdirector.png#asset:516

 

These channels, or any kind of sequence "items" can easily be added to other items, with optional serialization of their hierarchy and/or data.

mGameToSequenceCameraBlend = CreateChildItem("Game to Sequence cam");
mGameToSequenceCameraBlend->SetShouldSerializeHierarchy(false);
mGameToSequenceCameraBlend->SetDefaultValue(1.f);

mLockCamBehindPlayer = CreateChildItem("Lock Behind Player");
mLockCamBehindPlayer->SetShouldSerializeHierarchy(false);
mLockCamBehindPlayer->SetDefaultValue(0.f);

mLockedPitchAngle = CreateChildItem("Locked Pitch Angle");
mLockedPitchAngle->SetShouldSerializeHierarchy(false);
mLockedPitchAngle->SetDefaultValue(0.f);

mLockCamUserInput = CreateChildItem("Lock Cam User Input");
mLockCamUserInput->SetShouldSerializeHierarchy(false);
mLockCamUserInput->SetDefaultValue(1.f);

Resources

Sequence Resources are a common problem in these kinds of systems. A resource is any kind of externally bound data that the sequence can used. For example, an entity of the world. This can be a prefixed entity that only exists once, or the sequence can be instanced and the resource redefined as part of that instance.

I really doubled down on this idea of sequences being a hierarchy, thus this is also visible in the resource system. A "game camera" sequence item, for example, expects it to be parent to a valid player agent:

void GCL_SequenceGameCameraInstance::AddToWorld()
{
    GCL_SequenceEntityResource::Instance* entityRes = GetParentResourceInstance< GCL_SequenceEntityResource >();

    if (!entityRes || !entityRes->mEntityHandle.IsValid())
    {
        WAR_LOG_ERROR(CAT_GENERAL, "Failed to get entity");
        return;
    }

    if (GCL_PlayerAgentComponent* agent = entityRes->mEntityHandle.GetEntity()->GetComponentContainer().GetComponentByType< GCL_PlayerAgentComponent >())
    {
        if (agent->GetCamera())
        {
            mComponentHandle = agent->GetCamera()->GetHandle();
        }
    }

    if (mComponentHandle.IsValid() == false)
    {
        WAR_LOG_WARNING(CAT_GENERAL, "Failed to resolve camera handle, is the parent item a PlayerAgentComponent?");
    }
}

Sequence resources can be defined as part of the sequence itself:

seqresource_base.png#asset:518

 

When reusing a sequence in a template, its resources can be individually overridden when needed to provide a custom binding:

seqresource.png#asset:517

Animation

The Animation system of Warlock is driven by a nodegraph, and supports "variables" to communicate to gameplay code. Variables can also be driven from sequences.

State Machines

animedit_statemachine.png#asset:526

 

A big part of animation systems is a state machine. Warlock supports state machines with a variety of ways to transition to/from states. Either from code, by animation end or by expression from within the node graph:

animedit_stateenter.png#asset:527

Curves
Audio

Warlock uses a node graph to define audio events. They are fairly simple still, but you can also define different effects (reverb, pitch shift, etc.) here.

audioedit.png#asset:528

There are also many lower level systems that solve a variety of problems.

Object System

A small type/object system is used within many systems of Warlock that allows for easy registration of new types and safe inheritance casting.

For example, to register new sequence types:

seq->GetItemTypeRegistry().RegisterType< GCL_SequenceFadeTrack >();
seq->GetItemTypeRegistry().RegisterType< GCL_SequenceEntityItem >();
seq->GetItemTypeRegistry().RegisterType< GCL_SequenceAudioEventTrack >();
seq->GetItemTypeRegistry().RegisterType< GCL_SequenceAudioBusVolumeTrack >();
seq->GetItemTypeRegistry().RegisterType< GCL_SequenceEntityTransformTrack >();
seq->GetItemTypeRegistry().RegisterType< GCL_SequenceGameCameraTrack >();

But also entity components:

lvl->RegisterEntityComponent< GCL_MeshComponent >();
lvl->RegisterEntityComponent< GCL_LightComponent >();
lvl->RegisterEntityComponent< GCL_AnimControllerComponent >();
lvl->RegisterEntityComponent< GCL_SequenceComponent >();
lvl->RegisterEntityComponent< GCL_ReflectionProbeComponent >();
lvl->RegisterEntityComponent< GCL_AgentComponent >();
lvl->RegisterEntityComponent< GCL_PlayerAgentComponent >();
lvl->RegisterEntityComponent< GCL_AgentSpawnComponent >();
lvl->RegisterEntityComponent< GCL_JiggleComponent >();
lvl->RegisterEntityComponent< GCL_ThirdPersonCameraComponent >();
lvl->RegisterEntityComponent< GCL_IBLProbeGridComponent >();
lvl->RegisterEntityComponent< GCL_InteractionIconComponent >();
lvl->RegisterEntityComponent< GCL_InteractionDispatchComponent >();
lvl->RegisterEntityComponent< GCL_GameCameraComponent >();

Objects can also store custom statically defined type info that is registered per object type. For example, this stores some editor related data:

void GCL_SequenceGameCameraTrack::ObjectRegisterTypeInfo(SEQ_ItemTypeInfo& info)
{
#ifdef USING_TOOL_EDITOR
    info.mIsUserSelectable = true;
    info.AddRequiredParent< GCL_SequenceEntityItem >();
    info.mDisplayName = "Player Camera";
#endif
}
3rd party libraries

I try to use as few external dependencies as possible so that I remain in control over all low level elements of the engine.

The libraries I use are:

  1. ANGLE
    An OpenGL emulation layer made by Google for their Chrome browser. They use it to emulate OpenGL/WebGL with DirectX. I use it internally while developing Warlock for HTML5, so that I can test, verify and debug WebGL functionality within Windows' debug environment.
  2. Basis Universal
    A texture compression algorithm that has the added benefit of encoding texture to a "intermediate" format, that can then be transcoded to a number of formats depending on the platform. This makes it an ideal solution for cross-platform texture compression. In Warlock, textures are encoded on the fly during development, when exporting a project for deployment all textures are baked to their encoded variant.
  3. Bullet Physics
    A well known physics library used in many commercial games. I use it for all physics simulation in the engine, as well as for some handy algorithms. I used Bullet Physics to develop a character controller, trigger boxes, etc.
  4. plf::colony
    A low level library that introduces a new type of storage for unsorted elements. It is ideal in a situation where you don't care about the order of the elements, want to keep pointers to objects static (no reallocation), but not want to preallocate all storage in advance. Colony allocates blocks of elements, with skip fields to optimize the search / insert / remove functions. It has since been introduced in the C++ standard library as std::colony.
  5. discord rpc
    This is Discord's integration library for providing "rich experiences" in Discord - aka showing a status indication of the user that they are in your app, with some extra info.  
  6. ExprTk
    Expression Toolkit (exprtk) is a library that can parse and execute mathematical expressions. It is used in Warlock in a dedicated Expression Node in which you can alter the inputs using a text expression. This makes it much easier to create complex expressions in graphs. I added a custom parser to this that allows you to define the node's input pins through a similar syntax.
  7. FBX SDK
    A very common format for storing 3D models/animations. It was created by Autodesk and is still used as the de facto standard in the game/movie industry.
  8. FluidSynth
    One of the "odder" libraries in Warlock, FluidSynth is a Midi synthesizer that uses the SoundFont format. I refactored it to remove all external dependencies so that it can compile with the cross-platform abstraction layers of Warlock. I also added functionality to fade the individual channels of the synthesizer to support the trick in older games to store different song variations within the same track by muting specific channels.
  9. FreeType
    FreeType is a font rendering library used while in development mode, to provide a cleaner font for ImGui.
  10. glsl optimizer
    During the packaging of projects that use OpenGL as renderbackend, this library is used to optimize the generated output, unroll loops and shorten assignments. It's very effective!
  11. hlslparser
    Shaders in Warlock are written in HLSL, and then converted into GLSL where necessary using this library. I added some functionality to this to support a few more obscure shader operations.
  12. ImGui
    One of the best libraries ever made! All development UI in Warlock is made using ImGui.
  13. ImGuizmo
    A great library that gives you a powerful 3D gizmo for free!
  14. Live++
    Adds support for hotreloading of code while Warlock is running. While the license is expensive, it allows for super quick iteration of algorithms and tweaking of gameplay behaviors.
  15. LZO
    A compression library that provides faster/realtime but weaker compression than ZLIB.
  16. MCPP
    A C preprocessor. I build it as a library as part of the Warlock codebase to preprocess shaders essentially as C files. This way shaders can use macros and #includes like normal code files.
  17. MiniMP3
    Warlock has a custom sound decoding solution and I use MiniMP3 to decode MP3s specifically. The plan is to support Vorbis as the standard compression for audio, though. This is more meant to support projects where the MP3 format is already used.
  18. NoesisGUI
    NoesisGUI is the in-game UI solution that Warlock uses. It is the only licensed library in Warlock for actual game production, therefore it is not build as part of the standard framework and can be optionally included.
  19. OpenAL
    Cross-platform 3D audio API that is used as low level audio driver in Warlock.
  20. RapidJSON
    JSON is used everywhere in Warlock to store assets in an intermediate format before packaging. Warlock deploys a custom "variant map" solution that is used for serialization. During packaging, all text JSON files are baked to optimized binary files that can be quickly loaded without needing any additional deserialization/parsing before their contents can be accessed. I also modified RapidJson to support a few handy custom types that make things more readable. For example, 3D vectors can be expressed like this:
    "translation": vec(-4.915815353393555, 4.652518272399902, 0.0)
  21. SDL
    Used as an OS abstraction for certain platforms, or for platforms before a native abstraction is written.
  22. SoLoud
    Audio decoding/mixing library that supports a flexible effects pipeline.
  23. stb image
    Used for decoding basic image formats (jpeg, png, etc.).
  24. ZLIB
    Used for compression/decompression of assets.

I've used Warlock for many projects already. Let's see some of them in action!

Character controller demo
Dinosaur Planet map viewer
Dinosaur Planet filesystem utilities
Quantic Dream modding tools
Advanced plotting tools with node graph
N64 emulator "IDE"