Some examples of C++ scripts. Share yours too

If you too are waiting for the C++ docs to be written, but cannot wait to code and are struggling with it, why not share here examples of code you made, or common mistakes you encounter.


Flax’s C++ Manual : https://docs.flaxengine.com/manual/scripting/cpp/index.html
Flax’s API tags doc : https://docs.flaxengine.com/manual/editor/flax-build/api-tags.html
Flax’s GitHub sources : https://github.com/FlaxEngine/FlaxEngine/tree/1.0/Source


To enable C++ script in you project you must open the file “ProjectFolder/Source/Game/Game.Build.cs” and change this line to true :

BuildNativeCode = true;

otherwise it will not compile and Flax’s build tool will ignore all C++ script.


Here a bunch of includes you might need :

#include “Engine/Scripting/Script.h”
#include “Engine/Input/Input.h”
#include “Engine/Core/Log.h”
#include “Engine/Core/Math/Transform.h”
#include “Engine/Core/Math/Vector3.h”
#include “Engine/Level/Actor.h”
#include “Engine/Level/Level.h”
#include “Engine/Level/Actors/StaticModel.h”
#include “Engine/Level/Actors/Camera.h”
#include “Engine/Physics/Actors/RigidBody.h”
#include “Engine/Physics/Colliders/CharacterController.h”
#include “Engine/Physics/Colliders/SphereCollider.h”
#include “Engine/Engine/Screen.h”
#include “Engine/Engine/Time.h”
#include “Engine/Input/Input.h”
#include “Engine/Content/Asset.h”
#include “Engine/Content/Assets/Model.h”
#include “Engine/Content/Cache/AssetsCache.h”


How to use log :

#include “Engine/Core/Log.h” // <- Don’t forget to include this.
int foo = 4;
bool bar = true;
LOG(Info, “Log are variadic, here my first variable {0}, here the second {1}, etc…”, foo, bar);


For using OnFixedUpdate() function you must enable it in the script’s constructor, otherwise it will never be called:

_tickFixedUpdate = true;


Using API tag to show public member of yours script in Flax’s editor properties window :

public:
API_FIELD() float radial_distance = 1000.0f;
API_FIELD() float latitude = 0.0f;
API_FIELD() float longitude = 0.0f;
API_FIELD() Vector3 orbit = Vector3(0, 70, 0);


How to spawn RigidBody, with loaded model and collider :

// Create rigid body
RigidBody* ball = New<RigidBody>();
ball->SetName(String("Bullet"));
ball->SetStaticFlags(StaticFlags::None);
ball->SetUseCCD(true);

// Loading sphere assets
AssetReference<Model> model;
AssetInfo info;
String path("C:\\Program Files (x86)/Flax/Flax_1.0/Content/Engine/Models/Sphere.flax");
if(Content::GetAssetInfo(path, info)) {
	LOG(Info, "LOADED ASSET !!!");
	model.Set(dynamic_cast<Model*>(LoadAsset(info.ID, Model::TypeInitializer)));
} else LOG(Info, "NOT LOADED ASSET !!!");


// Creating static model
StaticModel* ballModel = New<StaticModel>();
ballModel->SetName(String("Model ball"));
ballModel->Model = model;
ballModel->SetParent(ball);
ballModel->SetStaticFlags(StaticFlags::None);

// Creating collider
SphereCollider* ballCollider = New<SphereCollider>();
ballCollider->SetName(String("Ball collider"));
ballCollider->SetParent(ball);
ballCollider->SetStaticFlags(StaticFlags::None);


// Set translation, orientation and scale of rigidbody
ball->SetTransform(Transform(Vector3(0, 8, 0), Quaternion::Identity, Vector3(1)));

// Add it to the world
Level::SpawnActor(ball);

// Destroy actor after 5 seconds of in game time.
ball->DeleteObject(5.0f, true);

Lock and hide the mouse :

Screen::SetCursorLock(CursorLockMode::Locked);
Screen::SetCursorVisible(false);

Unlock and show the mouse :

Screen::SetCursorLock(CursorLockMode::None);
Screen::SetCursorVisible(true);


Find actor by name:

GetActor()->FindActor(String(“Camera”));

Add force to a RigidBody :

dynamic_cast<RigidBody*>(GetActor())->AddForce(speed*cam->GetDirection(), ForceMode::Impulse);


Get input keyboard.

if(Input::GetAction(String(“Name input in settings.”))) { /*Stuff to do on input*/ }


Get input mouse.

float mouseX = Input::GetAxis(String(“Name mouse X in settings.”));


Add movement for a CharacterController:

In your .h :

public:
	API_FIELD() float gravity = -980.665f;
	API_FIELD() float speed = 500;
	API_FIELD() float jump = 500;

private:
	CharacterController* _controller;
	Vector3 _velocity = Vector3(0.0f);

In your .cpp :

void Player::OnFixedUpdate()
{
	//LOG(Info, "PLAYER's OnFixedUpdate CALLED !!!!!");
	auto t = Time::GetDeltaTime();

	// Apply gravity on velocity if not grounded (physics).
	// V1 = -g*t+V0
	if(!_controller->IsGrounded()) _velocity.Y = gravity*t + _velocity.Y;

	// PLayer's input
	_velocity.X = 0;
	_velocity.Z = 0;
	if(_controller->IsGrounded()) {
		_velocity.Y = 0;
		if(Input::GetAction(String("Jump"))) {
			_velocity.Y = jump;;
		}
	}
	if(Input::GetAction(String("Left"))) {
        _velocity.X = -speed;
	}
	if(Input::GetAction(String("Right"))) {
        _velocity.X = speed;
	}
	if(Input::GetAction(String("Up"))) {
        _velocity.Z = speed;
	}
	if(Input::GetAction(String("Down"))) {
        _velocity.Z = -speed;
	}

	// Translation is an aproximation from velocity.
	Vector3 translation = _velocity*t;
    _controller->AddMovement(translation, Quaternion::Identity);
}

How to make a procedural mesh (and not a procedural mess) :

void YourScript::OnEnable()
{
	// Only virtual model can be edited on the fly.
    AssetReference<Model> virtual_model = Content::CreateVirtualAsset<Model>();
    // Setting the number of mesh per LOD.
    // Span<T> are universal representation of a contiguous region of arbitrary memory.
    // Here we want to make space for an array of one single integer, who's value will be
    // the number of meshs per LOD we want. We just want one.
    const int32 meshesCountPerLod = 1; 
    Span<int32> spanMeshesCountPerLod(&meshesCountPerLod, sizeof(int32));
    virtual_model->SetupLODs(spanMeshesCountPerLod);

    // Generating procedural mesh and sending data to the GPU, be carefull, it returning FALSE IF SUCCESSFUL !
    if(UpdateMesh(virtual_model->LODs[0].Meshes[0])) LOG(Info, "Mesh NOT updated.");
    else LOG(Info, "Mesh updated !!!");

	// Creating static model, add it to actor
	StaticModel* procedural_static_model = New<StaticModel>();
	procedural_static_model->SetName(String("Procedural Static Model"));
	procedural_static_model->Model = virtual_model;
	procedural_static_model->SetParent(GetActor());
	procedural_static_model->SetStaticFlags(StaticFlags::None);
	procedural_static_model->SetTransform(Transform(GetActor()->GetPosition(), Quaternion::Identity, Vector3(1)));
	// Say we are using LODs[0] for all distance, we do this so if the camera is not near from the model, it will not switch to the next LOD,
	// and because there is none other, this would make the model invisible otherwise.
	procedural_static_model->SetForcedLOD(0);

}

bool YourScript::UpdateMesh(Mesh& mesh)
{
	// We scale the point by x100, Flax unit are centimeters,
	// if we don't do that you model will be so small you wouldn't be able to see it.
    const float X = 0.525731112119133606f * 100;
    const float Z = 0.850650808352039932f * 100;
    const float N = 0.0f;
    Vector3 vertices[] = {
        Vector3(-X, N, Z),
        Vector3(X, N, Z),
        Vector3(-X, N, -Z),
        Vector3(X, N, -Z),
        Vector3(N, Z, X),
        Vector3(N, Z, -X),
        Vector3(N, -Z, X),
        Vector3(N, -Z, -X),
        Vector3(Z, X, N),
        Vector3(-Z, X, N),
        Vector3(Z, -X, N),
        Vector3(-Z, -X, N)
    };
    uint32 triangles[] = {
        1, 4, 0, 4, 9, 0, 4, 5, 9, 8, 5, 4,
        1, 8, 4, 1, 10, 8, 10, 3, 8, 8, 3, 5,
        3, 2, 5, 3, 7, 2, 3, 10, 7, 10, 6, 7,
        6, 11, 7, 6, 0, 11, 6, 1, 0, 10, 1, 6,
        11, 0, 9, 2, 11, 9, 5, 2, 9, 11, 2, 7
    };

    // Most functions in flax return true if somethings fail, it a little counter-intuitive
    // but it's important you know and don't forget that. It's for avoiding to add ! to condition clause.
    // This is the case of this function, it will return false if successful.
    return mesh.UpdateMesh(
        (uint32) 12,    // uint32 vertexCount
        (uint32) 20,    // uint32 triangleCount
        vertices,
        triangles
    );
}

I may edit this post to add more examples. Don’t hesitate to post your finding here too.

3 Likes

How to handle JSON Assets!

I’ve been trying to expand the C++ stuff on the docs, heres how you can create and access your very own JSON Asset!

Declaring


#pragma once

#include <Engine/Core/ISerializable.h>
#include <Engine/Core/Types/BaseTypes.h>
#include <Engine/Content/Assets/Model.h>
#include <Engine/Scripting/ScriptingType.h>

/// <summary>
/// Contains data 
/// </summary>
API_CLASS() class GAME_API DataExample : public ISerializable
{
public:

    API_AUTO_SERIALIZATION();
    DECLARE_SCRIPTING_TYPE_NO_SPAWN(DataExample);

public:

    API_FIELD(Attributes = "Range(0, 20), EditorOrder(0), EditorDisplay(\"Data\")") 
    float Field1 = 20.0f;

    API_FIELD(Attributes = "Range(1000, 10000), EditorOrder(1), EditorDisplay(\" Data\")")
    float Field2 = 1000.0f;

    API_FIELD(Attributes = "Limit(0, 20), EditorOrder(2), EditorDisplay(\"Data\")")
    int32 Field3 = 10;

    API_FIELD(Attributes = "EditorOrder(3), EditorDisplay(\"Data\")")
    Vector3 Field4 = Vector3(0.1f);
};

Accessing

#pragma once

#include "Engine/Scripting/Script.h"
#include <Engine/Core/Types/BaseTypes.h>
#include <Engine/Content/JsonAsset.h>
#include <Engine/Debug/DebugLog.h>
#include <Engine/Content/AssetReference.h>

API_CLASS() class GAME_API MyScript : public Script
{
API_AUTO_SERIALIZATION();
DECLARE_SCRIPTING_TYPE(MyScript);

public:

    API_FIELD(Attributes = "AssetReference(typeof(DataExample))")
    AssetReference<JsonAsset> MyData;

    // [Script]
    void OnEnable() override;
    void OnDisable() override;
    void OnUpdate() override;
    void OnStart() override
    {
        auto obj = (DataExample*)MyData.Get()->Instance;
    	DebugLog::Log(String::Format(TEXT("{0}"),  obj->Field2));
    }
};
4 Likes