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.