Unreal.hx

Using Haxe with the Unreal Engine 4

 

https://slides.com/cauewaneck/unreal-hx/live

By Cauê Waneck ( @cwaneck )

[~]$ whoami

Haxe in World Zombination

  • Technology-agnostic game logic
  • Free to choose the technology that best suits us
  • Run same game logic on both client and server
  • Used macros to transform synchronous code in asynchronous
  • Static typing + concise language
  • Native access
  • Chosen by many AAA games
  • Targets all major platforms - desktop, mobile and console
  • VR support
  • Source code available!
  • Great out-of-the-box multiplayer support
  • C++ is the only official language
C++ can be difficult to integrate with itself let alone other languages
  • Supports also visual coding with Blueprints

Unreal.hx !

  • Uses Haxe's C++ backend
  • Write everything in Haxe
  • Use external C++ code as easily as possible
  • Compatible with the cppia VM
  • DCE compatible
  • Support EVERY C++ feature

Why Unreal.hx?

  • Garbage collection
  • Porting from C++ to Haxe is easy
  • Partition between C++ and Haxe
  • Hxcpp for performance, cppia+@:live for fast iteration
  • Most feature-rich language binding for Unreal
  • Free, Open Source Software
  • Target other platforms - e.g. server

Streamline

Streamline

Streamline

Closed beta this year!

The big picture

  • Needs latest Haxe 3.3+
  • Targets Unreal 4.11

Externs

package unreal;

/**
  Actor is the base class for an Object that can be placed or spawned in a level.
  Actors may contain a collection of ActorComponents, which can be used to control how actors move, how they are rendered, etc.
  The other main function of an Actor is the replication of properties and function calls across the network during play.
  
  @see https://docs.unrealengine.com/latest/INT/Programming/UnrealArchitecture/Actors/
  @see UActorComponent
**/
@:glueCppIncludes("GameFramework/Actor.h")
@:uextern extern class AActor extends unreal.UObject {
  
  /**
    Array of ActorComponents that are created by blueprints and serialized per-instance.
  **/
  public var BlueprintCreatedComponents : unreal.TArray<unreal.UActorComponent>;

  #if WITH_EDITORONLY_DATA
  /**
    Local space pivot offset for the actor
  **/
  private var PivotOffset : unreal.FVector;
  #end // WITH_EDITORONLY_DATA

  /**
    Returns the point of view of the actor.
    Note that this doesn't mean the camera, but the 'eyes' of the actor.
    For example, for a Pawn, this would define the eye height location,
    and view rotation (which is different from the pawn rotation which has a zeroed pitch component).
    A camera first person view will typically use this view point. Most traces (weapon, AI) will be done from this view point.
    
    @param       OutLocation - location of view point
    @param       OutRotation - view rotation of actor.
  **/
  @:thisConst public function GetActorEyesViewPoint(OutLocation : unreal.PRef<unreal.FVector>, OutRotation : unreal.PRef<unreal.FRotator>) : Void;
  
  /**
    Script exposed version of FindComponentByClass
  **/
  public function GetComponentByClass(ComponentClass : unreal.TSubclassOf<unreal.UActorComponent>) : unreal.UActorComponent;
package unreal;

/**
  Actor is the base class for an Object that can be placed or spawned in a level.
  Actors may contain a collection of ActorComponents, which can be used to control how actors move, how they are rendered, etc.
  The other main function of an Actor is the replication of properties and function calls across the network during play.
  
  @see https://docs.unrealengine.com/latest/INT/Programming/UnrealArchitecture/Actors/
  @see UActorComponent
**/
@:glueCppIncludes("GameFramework/Actor.h")
@:uextern class AActor extends unreal.UObject {

  /**
    Array of ActorComponents that are created by blueprints and serialized per-instance.
  **/
  public var BlueprintCreatedComponents(get,set):unreal.PPtr<unreal.TArray<unreal.UActorComponent>>;

  @:glueCppIncludes("GameFramework/Actor.h", "uhx/Wrapper.h")
  @:glueHeaderIncludes("IntPtr.h", "VariantPtr.h")
  @:glueHeaderCode("static void GetActorEyesViewPoint(unreal::UIntPtr self, unreal::VariantPtr OutLocation, unreal::VariantPtr OutRotation);")
  @:glueCppCode("void uhx::glues::AActor_Glue_obj::GetActorEyesViewPoint(unreal::UIntPtr self, unreal::VariantPtr OutLocation, unreal::VariantPtr OutRotation) {\n\t( (AActor *) self )->GetActorEyesViewPoint(*::uhx::StructHelper< FVector >::getPointer(OutLocation), *::uhx::StructHelper< FRotator >::getPointer(OutRotation));\n}")
  @:thisConst
  public function GetActorEyesViewPoint(OutLocation : unreal.PRef<unreal.FVector>, OutRotation : unreal.PRef<unreal.FRotator>) : Void {
    uhx.glues.AActor_Glue.GetActorEyesViewPoint(unreal.helpers.HaxeHelpers.getUObjectWrapped(this), OutLocation, OutRotation);
  }

  @:glueCppIncludes("GameFramework/Actor.h", "uhx/Wrapper.h", "Containers/Array.h", "Components/ActorComponent.h", "uhx/glues/TArrayImpl_Glue_UE.h")
  @:glueHeaderIncludes("IntPtr.h", "VariantPtr.h")
  @:glueHeaderCode("static unreal::VariantPtr get_BlueprintCreatedComponents(unreal::UIntPtr self);")
  @:glueCppCode("unreal::VariantPtr uhx::glues::AActor_Glue_obj::get_BlueprintCreatedComponents(unreal::UIntPtr self) {\n\treturn ::uhx::TemplateHelper<TArray<UActorComponent *>>::fromPointer( (&(( (AActor *) self )->BlueprintCreatedComponents)) );\n}")
  @:final @:nonVirtual
  private function get_BlueprintCreatedComponents() : unreal.PPtr<unreal.TArray<unreal.UActorComponent>> {
    return ( @:privateAccess unreal.TArrayImpl.fromPointer( uhx.glues.AActor_Glue.get_BlueprintCreatedComponents(unreal.helpers.HaxeHelpers.getUObjectWrapped(this)) ) : unreal.PPtr<unreal.TArray<unreal.UActorComponent>> );
  }

  /**
    Script exposed version of FindComponentByClass
  **/
  @:glueCppIncludes("GameFramework/Actor.h", "UObject/ObjectBase.h", "Components/ActorComponent.h")
  @:glueHeaderIncludes("IntPtr.h")
  @:glueHeaderCode("static unreal::UIntPtr GetComponentByClass(unreal::UIntPtr self, unreal::UIntPtr ComponentClass);")
  @:glueCppCode("unreal::UIntPtr uhx::glues::AActor_Glue_obj::GetComponentByClass(unreal::UIntPtr self, unreal::UIntPtr ComponentClass) {\n\treturn ( (unreal::UIntPtr) (( (AActor *) self )->GetComponentByClass(( (TSubclassOf<UActorComponent>) (UClass *) ComponentClass ))) );\n}")
  public function GetComponentByClass(ComponentClass : unreal.TSubclassOf<unreal.UActorComponent>) : unreal.UActorComponent {
    return ( cast unreal.UObject.wrap(uhx.glues.AActor_Glue.GetComponentByClass(unreal.helpers.HaxeHelpers.getUObjectWrapped(this), unreal.helpers.HaxeHelpers.getUObjectWrapped(ComponentClass))) : unreal.UActorComponent );
  }

Externs

// you define templated functions and types!
@:glueCppIncludes('Templates/SharedPointer.h')
@:uextern extern class TSharedPtr<T> {
  @:global
  public static function MakeShareable<T>(ptr:PPtr<T>):TSharedPtr<T>;

  /**
   * Constructs an empty shared pointer
   */
  @:uname('.ctor') public static function create<T>():TSharedPtr<T>;
  /* snip ... */
}

// you can also define C++ enums!
@:glueCppIncludes("IHttpRequest.h")
@:uname("EHttpRequestStatus.Type")
@:uextern extern enum EHttpRequestStatus {
  NotStarted;
  Processing;
  Failed;
  Succeeded;
}

// and delegates! Yes you can bind normal Haxe functions to them
@:glueCppIncludes("IHttpRequest.h")
@:uname('FHttpRequestCompleteDelegate')
typedef FHttpRequestCompleteDelegate = Delegate<FHttpRequestCompleteDelegate, TSharedPtr<IHttpRequest>->TThreadSafeSharedPtr<IHttpResponse>->Bool->Void>;

// also C++ method pointers:
/**
 * Binds a delegate function to an Action defined in the project settings.
 * Returned reference is only guaranteed to be valid until another action is bound.
*/
public function BindAction(actionName:Const<FName>, keyEvent:EInputEvent, object:UObject, func:MethodPointer<UObject,Void->Void>) : PRef<FInputActionBinding>;


// also some operators are supported
@:glueCppIncludes("Array.h")
@:uextern @:noCopy extern class TIndexedContainerIterator<Ar, El, Ind> {
  public function op_Increment() : Void;
  public function op_Decrement() : Void;
  public function op_Dereference() : PRef<El>;
  public function op_Not() : Bool;
}

Static/Scripts

package platformer;
import unreal.*;

using unreal.CoreAPI;

// define a new delegate
typedef FRoundFinishedDelegate = DynamicMulticastDelegate<FRoundFinishedDelegate, Void->Void>;

// you can define your own UENUMs through Haxe - which can be used by C++!
@:uenum
enum EGameState {
  Intro;
  Waiting;
  Playing;
  Finished;
  Restarting;
}

@:uclass
// you can use @:uname to define the target name, without having to use that
@:uname("APlatformerGameMode")
class GameMode extends AGameMode {
  var someHaxeArray:Array<String>;
  var someUnrealArray:TArray<Int8>;
  @:uexpose var someExposedUnrealArray:TArray<FString>;

  /** delegate to broadcast about finished round */
  @:uproperty(BlueprintAssignable)
  public var OnRoundFinished:FRoundFinishedDelegate;

  public function someHaxeFunction(json:{ name:String, age:Int }):Void { /* ... */ }

  @:uexpose public function someUnrealFunction(char:Character, basicType:Bool):Void { /* ... */ }
  /* snip .. */
}

@:uclass(Abstract)
@:uname("APlatformerCharacter")
class Character extends ACharacter {
  /** player pawn initialization */
  override public function PostInitializeComponents():Void {
    super.PostInitializeComponents();

    SetActorRotation(FRotator.createWithValues(0,0,0));
  }
  /* snip ... */
}

Unreal Types

UObjects

  • Always have special reflection metadata
  • Unreal garbage collection
  • Virtual functions, always pointers

"Structs"

  • May have reflection metadata (USTRUCT)
  • Manual management
  • May be accessed by value or by reference/pointer

in c++

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "PlatformerClimbMarker.generated.h"

UCLASS(Blueprintable)
class APlatformerClimbMarker : public AActor
{
	GENERATED_UCLASS_BODY()

private:
	UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category=Mesh, meta=(AllowPrivateAccess="true"))
	UStaticMeshComponent* Mesh;

public:
	/** Returns Mesh subobject **/
	FORCEINLINE UStaticMeshComponent* GetMesh() const { return Mesh; }
};
class FPlatformerMessageData
{
	/** text to display */
	FString Message;
	
	/** how long this FMessageData will be displayed in seconds */
	float DisplayDuration;

	/** TimeSeconds when this FMessageData was first shown */
	float DisplayStartTime;
	
	/** x axis position on screen <0, 1> (0 means left side of the screen) ; text will be centered */
	float PosX;
	
	/** y axis position on screen <0, 1> (0 means top of the screen) ; text will be centered */
	float PosY;
	
	/** text scale */
	float TextScale;
	
	/** if red border should be drawn instead of blue */
	bool bRedBorder;
};

UObjects

  • Still depend on @:uproperty metas :(
  • All Haxe types are weak pointers - no crash :)
  • Normal hxcpp wrapper: can be cast, interfaces...
  • Unreal.hx knows that it's accessed via pointer

"Structs"

  • If haxe created the type, it can be gc'd :)                                                                                   
  • External pointers don't need wrappers!
  • Need to specify how the C++ type is accessed

in Haxe

"Struct" types

C++ Haxe
FSomeStruct FSomeStruct
FSomeStruct& PRef<FSomeStruct>
FSomeStruct * PPtr<FSomeStruct> or POwnedPtr<FSomeStruct>
const FSomeStruct& Const<PRef<FSomeStruct>>
FSomeStruct ** <not supported... yet>

Could this be generalized?

  • Short answer: It'd need a little work, but probably

Future plans

  • Use Unreal hooks to build the project
  • Fix Unreal header includes
  • Optimize C++ build speed
  • Cppia generate UClasses, UFunctions and UProperties directly
  • General Pointer type
  • Automatically generate all C++ types externs
  • General incremental support

Unreal Memory Model

UObjects

  • Game objects - all derive from the UObject class
  • Have either A or U prefix on their name
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "PlatformerClimbMarker.generated.h"

UCLASS(Blueprintable)
class APlatformerClimbMarker : public AActor
{
	GENERATED_UCLASS_BODY()

private:
	UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category=Mesh, meta=(AllowPrivateAccess="true"))
	UStaticMeshComponent* Mesh;

public:
	/** Returns Mesh subobject **/
	FORCEINLINE UStaticMeshComponent* GetMesh() const { return Mesh; }
};

UObjects

  • Unreal-driven reflection using a header parser
  • Garbage collected - uses reflection to know the references to visit
    • Only valid references are member variables with the UPROPERTY metadata
  • Can be replicated, accessed by blueprints, RPCs...
  • Only accessed as a pointer/reference, never by value
  • Have a special TWeakObjectPtr so you can check if the underlying object was garbage collected

Everything Else

  • Have the T, F or I prefix
  • May be passed by value, reference, pointer or shared pointer:
    • FVector, FVector&, const FVector&, FVector *, TSharedPtr<FVector>, const TSharedPtr<const FVector>&
  • Returning a reference/pointer is uncommon
  • Reference/pointer as an argument are short-lived
  • Shared pointers (refcounting) are usually used instead of raw pointers

Using them in Unreal.hx

UObject-derived

  • Wrapped by Haxe
  • Unreal still controls its lifetime - same UPROPERTY rules apply
  • All wrappers are weak pointers - check the lifetime with `isValid()` function call
  • You can extend classes, override functions, etc

UObject-derived

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "PlatformerClimbMarker.generated.h"

UCLASS(Blueprintable)
class APlatformerClimbMarker : public AActor
{
	GENERATED_UCLASS_BODY()

private:
	UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category=Mesh, meta=(AllowPrivateAccess="true"))
	UStaticMeshComponent* Mesh;

public:
	/** Returns Mesh subobject **/
	FORCEINLINE UStaticMeshComponent* GetMesh() const { return Mesh; }
};
package platformer;
import unreal.*;

@:uclass(Blueprintable)
@:uname("APlatformerClimbMarker")
class ClimbMarker extends AActor {
    @:uproperty(VisibleDefaultsOnly, BlueprintReadOnly, Category=Mesh, meta=[AllowPrivateAccess="true"])
    private var Mesh:UStaticMeshComponent;

    inline public function GetMesh() {
        return Mesh;
    }
}

Non-UObjects

  • If target type is a pointer, it will not create a wrapper for it, and use the raw pointer instead
  • When creating a new type, or getting a struct by value, a very small wrapper is allocated.
  • This wrapper allows Haxe garbage collection to take care of the type's ownership
  • You may declare new types, but you cannot override functions nor add Haxe-only types

Non-UObjects

C++ Haxe
FSomeStruct FSomeStruct
FSomeStruct& PRef<FSomeStruct>
FSomeStruct * PPtr<FSomeStruct> or POwnedPtr<FSomeStruct>
const FSomeStruct& Const<PRef<FSomeStruct>>
FSomeStruct ** <not supported... yet>

Non-UObjects

Uses Haxe abstracts!

package unreal;


/**
  Base class for all tick functions.
**/
@:glueCppIncludes("GameFramework/Actor.h")
@:uextern extern class FTickFunction {
  
  /**
    The frequency in seconds at which this tick function will be executed.  If less than or equal to 0 then it will tick every frame
  **/
  public var TickInterval : unreal.Float32;
  
  /**
    If false, this tick function will never be registered and will never tick. Only settable in defaults.
  **/
  public var bCanEverTick : Bool;  

  /* snip... */
}
package unreal;

/**
  Base class for all tick functions.  
**/
@:glueCppIncludes("GameFramework/Actor.h")
@:uextern
@:ueGluePath("uhx.glues.FTickFunction_Glue")
@:forward(dispose,isDisposed) abstract FTickFunction(unreal.Struct) to unreal.Struct to unreal.VariantPtr {
  /**
    The frequency in seconds at which this tick function will be executed.  If less than or equal to 0 then it will tick every frame    
  **/
  public var TickInterval(get,set):cpp.Float32;
  /**    
    If false, this tick function will never be registered and will never tick. Only settable in defaults.
  **/
  public var bCanEverTick(get,set):Bool;


  /**    
    Invokes the copy constructor of the referenced C++ class.
    This has some limitations - it won't copy the full inheritance chain of the class if it wasn't typed as the exact class
    it will also be a compilation error if the wrapped class forbids the C++ copy constructor;
    in this case, the extern class definition should contain the `@:noCopy` metadata
  **/
  @:glueCppIncludes("uhx/Wrapper.h", "GameFramework/Actor.h")
  @:glueHeaderIncludes("VariantPtr.h")
  @:glueHeaderCode("static unreal::VariantPtr copy(unreal::VariantPtr self);")
  @:glueCppCode("unreal::VariantPtr uhx::glues::FTickFunction_Glue_obj::copy(unreal::VariantPtr self) {\n\treturn ::uhx::StructHelper<FTickFunction>::fromStruct(FTickFunction(*::uhx::StructHelper< FTickFunction >::getPointer(self)));\n}")
  public function copy() : unreal.FTickFunction {
    return ( @:privateAccess unreal.FTickFunction.fromPointer( uhx.glues.FTickFunction_Glue.copy(this) ) : unreal.FTickFunction );
    
  }
  @:glueCppIncludes("<uhx/TypeTraits.h>", "uhx/Wrapper.h", "GameFramework/Actor.h")
  @:glueHeaderIncludes("VariantPtr.h")
  @:glueHeaderCode("static bool equals(unreal::VariantPtr self, unreal::VariantPtr other);")
  @:glueCppCode("bool uhx::glues::FTickFunction_Glue_obj::equals(unreal::VariantPtr self, unreal::VariantPtr other) {\n\tif (self.raw == other.raw) { return true; }if (self.raw == 0 || other.raw == 0) { return false; }return uhx::TypeTraits::Equals<FTickFunction>::isEq(*::uhx::StructHelper< FTickFunction >::getPointer(self), *::uhx::StructHelper< FTickFunction >::getPointer(other));\n}")
  public function equals(other : unreal.PPtr<unreal.FTickFunction>) : Bool {
    return uhx.glues.FTickFunction_Glue.equals(this, other);
  }

  @:glueCppIncludes("uhx/Wrapper.h", "GameFramework/Actor.h")
  @:glueHeaderIncludes("VariantPtr.h", "<hxcpp.h>")
  @:glueHeaderCode("static cpp::Float32 get_TickInterval(unreal::VariantPtr self);")
  @:glueCppCode("cpp::Float32 uhx::glues::FTickFunction_Glue_obj::get_TickInterval(unreal::VariantPtr self) {\n\treturn ::uhx::StructHelper< FTickFunction >::getPointer(self)->TickInterval;\n}")
  @:final @:nonVirtual 
  @:nonVirtual
  private function get_TickInterval() : cpp.Float32 {
    return uhx.glues.FTickFunction_Glue.get_TickInterval(this);
    
  }
  @:glueCppIncludes("uhx/Wrapper.h", "GameFramework/Actor.h")
  @:glueHeaderIncludes("VariantPtr.h", "<hxcpp.h>")
  @:glueHeaderCode("static void set_TickInterval(unreal::VariantPtr self, cpp::Float32 value);")
  @:glueCppCode("void uhx::glues::FTickFunction_Glue_obj::set_TickInterval(unreal::VariantPtr self, cpp::Float32 value) {\n\t::uhx::StructHelper< FTickFunction >::getPointer(self)->TickInterval = value;\n}")
  @:final @:nonVirtual 
  @:nonVirtual
  @:final @:nonVirtual 
  @:nonVirtual
  private function set_TickInterval(value : cpp.Float32) : cpp.Float32 {
    uhx.glues.FTickFunction_Glue.set_TickInterval(this, value);
    return value;    
  }

  @:glueCppIncludes("uhx/Wrapper.h", "GameFramework/Actor.h")
  @:glueHeaderIncludes("VariantPtr.h")
  @:glueHeaderCode("static bool get_bCanEverTick(unreal::VariantPtr self);")
  @:glueCppCode("bool uhx::glues::FTickFunction_Glue_obj::get_bCanEverTick(unreal::VariantPtr self) {\n\treturn ::uhx::StructHelper< FTickFunction >::getPointer(self)->bCanEverTick;\n}")
  @:final @:nonVirtual 
  @:nonVirtual
  private function get_bCanEverTick() : Bool {
    return uhx.glues.FTickFunction_Glue.get_bCanEverTick(this);
    
  }
  @:glueCppIncludes("uhx/Wrapper.h", "GameFramework/Actor.h")
  @:glueHeaderIncludes("VariantPtr.h")
  @:glueHeaderCode("static void set_bCanEverTick(unreal::VariantPtr self, bool value);")
  @:glueCppCode("void uhx::glues::FTickFunction_Glue_obj::set_bCanEverTick(unreal::VariantPtr self, bool value) {\n\t::uhx::StructHelper< FTickFunction >::getPointer(self)->bCanEverTick = value;\n}")
  @:final @:nonVirtual 
  @:nonVirtual
  @:final @:nonVirtual 
  @:nonVirtual
  private function set_bCanEverTick(value : Bool) : Bool {
    uhx.glues.FTickFunction_Glue.set_bCanEverTick(this, value);
    return value;
  }
  /* snip... */
}

Non-UObjects

No pass-by-value in Haxe!

struct FSceneView {
  bool WorldToPixel(const FVector& WorldPoint, 
                    FVector2D& OutPixelLocation);
};

// use like:
FVector2D pixelLocation;
Fvector somePoint = FVector(1,1,1);

FSceneView& view = getSomeView();
view.WorldToPixel(somePoint, pixelLocation);
// on Externs
extern class FSceneView {
  function WorldToPixel(WorldPoint:Const<PRef<FVector>>, 
                        OutPixelLocation:PRef<FVector2D>):Bool;
}

// use like:
var pixelLocation = FVector2D.create(); // we need to create this! otherwise it gets sent as null
var somePoint = new FVector(1,1,1);

var view = getSomeView();
view.WorldToPixel(somePoint, pixelLocation);

Cppia

  • HXCPP-compatible virtual machine
  • Compiles in a couple of seconds
  • Allows the creation of live functions, that can be reloaded in the same play-in-editor session!
  • Still needs a full C++ recompilation if a UPROPERTY, UFUNCTION, or a new UCLASS is declared/renamed

Wrapping up

  • Unreal is a modern, versatile and feature-rich 3D engine 
  • Haxe is the most feature-rich language binding other than the official C++
  • Low risk: you can easily partition your C++ and Haxe code as you'd like
  • Porting from C++ to Haxe is very straight-forward!

Wrapping up

  • Haxe is a high-level language that can target Unreal
  • Client/server interop
  • Leverage garbage collection even for non-UObject types!
  • Simpler memory model and familiar ECMA-inspired syntax
  • You can still access most C++ features from Haxe itself
  • Iterate quickly with cppia, and compile the final builds in C++ for better execution speed!
  • Hxcpp, Unreal.hx and Haxe are open-source!
  • We've reused lots of code!

Resources!

  • Unreal C++ documentation

Resources!

  • Unreal.hx documentation at github wiki page!

Resources!

  • Platformer Game sample project: waneck/HaxePlatformerGame
  • Check the commit history!

Questions?

Unreal.hx v2

By Cauê Waneck

Unreal.hx v2

  • 1,614