Your first component

Gameplay code lives in components: C++ classes you attach to objects in a scene. This page makes one that spins an object.

Create the component

  1. In the menu bar, select Scripts ▸ New C++ Component... to open the form for a new component.
  2. Enter Spinner as the Class name, leave the folder empty and select Create.

The editor writes Source/Spinner.h and Source/Spinner.cpp, opens the header in your code editor, and compiles the project. The header:

Source/Spinner.h
#pragma once

// the engine's umbrella header. It includes everything a game normally needs
// and brings `using namespace Shalltear` with it. Every engine header can
// still be included on its own
#include "Shalltear.h"

// a starter component: press Play and the object it is on rotates. Rename it
// or delete it once you have components of your own.
//
// fields listed with SLT_REFLECT_FIELD appear in the Inspector, are saved with
// the scene, and keep their values across a hot reload. Add one, save the file,
// and switch back to the editor to see it.
//
// NOTE: keep a field public while this component is the only thing reading it.
// Once another system holds its own copy of the value - a physics body's mass,
// a renderer's material - give the field private storage and a Get/Set pair,
// so the setter can tell that system the value changed. See Rigidbody.h
class Spinner : public Behavior {
public:
	// the per-frame hooks the engine calls on this component: Update calls
	// OnUpdate. C++ cannot tell which hooks a class overrides, so a component
	// names them here, and one that names none (BehaviorTick::None) costs
	// nothing per frame. To use more than one, combine them:
	// BehaviorTick::Update | BehaviorTick::LateUpdate
	Spinner() : Behavior(BehaviorTick::Update) {}

	SLT_REFLECT_COMPONENT(Spinner, Behavior)
	SLT_REFLECT_FIELD(degreesPerSecond)
	SLT_REFLECT_FIELD(target)
	SLT_END_REFLECT()

	// rotation speed in degrees per second. Editing it in the Inspector during
	// play takes effect on the next frame
	f32 degreesPerSecond = 60.0f;

	// which object to rotate. Left empty, this rotates the object the component
	// is attached to.
	//
	// NOTE: store a Handle, never a SceneObject*. Objects can be destroyed and
	// the pools holding them can move, so a resolved pointer is valid for the
	// current frame only. Resolve it when you need it, as OnUpdate does
	Handle<SceneObject> target;
protected:
	void OnEnable() override;
	void OnUpdate() override;
};

The source file, in a project named MyProject:

Source/Spinner.cpp
#include "Spinner.h"

// registers the class with the engine. Spinner appears in Add Component
// and its reflected fields appear in the Inspector once the module compiles
SLT_REGISTER_TYPE(Spinner)

void Spinner::OnEnable() {
	SLT_LOG_INFO("MyProject", "Spinner enabled");
}

void Spinner::OnUpdate() {
	// an empty handle and a destroyed object both resolve to nullptr, so one
	// check covers both
	SceneObject* object = target.Get();
	if (!object) {
		object = GetOwner().Get(); // nothing assigned, or the target is gone
	}
	if (!object) {
		return;
	}
	// GetDeltaTime() is the seconds since the last frame, so the speed is the
	// same at any frame rate. It is scaled game time and slows and stops with
	// the game; GetUnscaledDeltaTime() is real seconds
	const Quat step = Quat::AngleAxis(degreesPerSecond * Time::GetDeltaTime(), Vec3::Up());
	// normalized because a rotation accumulated frame by frame drifts off unit
	// length over time
	object->SetLocalRotation((object->GetLocalRotation() * step).Normalized());
}

The ExampleComponent your project started with is the same code under another name. Delete its two files whenever you like.

Put it on an object

  1. In the Hierarchy, select the + button, or right-click an empty area, and choose 3D Object ▸ Cube. The editor creates the cube and selects it.
  2. In the Inspector, select Add Component. Your own components come first, under your project's name. Select Spinner. If the list says the project is still compiling, wait for the compile to finish.
  3. Press Ctrl+P or the Play button in the title bar. The cube spins in the Runtime View.
  4. While the game plays, change degreesPerSecond in the Inspector. The cube changes speed on the next frame.
  5. Press Ctrl+P again to stop. Whatever you changed during play is undone when play stops.

Save the scene with Ctrl+S.

Change the code while the editor runs

  1. In Spinner.h, add SLT_REFLECT_FIELD(label) below the other two SLT_REFLECT_FIELD lines, and declare the field beside the others: str label = "spinner";.
  2. Save the file.

The editor sees the change, compiles and reloads your code on its own. Select the cube: label is in the Inspector, and degreesPerSecond still has the value you gave it.

If you save while the game is playing, the editor compiles straight away and loads the new code when you stop. To load it without stopping, select Reload Now in the title bar.

When the code does not compile

The Compile button shows the number of errors, and each error is a line in the Console. Double-click it to open the file at that line in your code editor. Until the next compile succeeds, the editor keeps running the last version of your code that compiled.

Next, build the game so it runs without the editor.