Actors
Overview
The ActorSystem
provides a convenient way to create custom entity behavior.
Setup
To setup a new actor follow these steps:
- Create a new controller class;
- Inherits from
ActorController
and / orUpdatableActorController
; - Create new quantum component for specific rollbackable actor data ( optional );
- Add non-rollbackable properties to controller;
- Override the
OnInitialize()
,OnDeinitialize()
andOnTick()
methods; - Write the entity logic;
- Rebuild Quantum solution; and,
- Use this controller type in Unity by creating a new instance of this class on the
EntityComponentController
component.
As shown in the diagram, the ActorController
class handles the initializing and deintializing an entity, while the UpdatableActorController
manages all things updatable.
Example Implementations
This section presents several examples of controllers included in the FPS Template.
Destructible Object Controller
C#
internal override sealed void Update(Frame frame, EntityRef entity)
{
// 1. Check Health state every frame
Health* health = frame.GetComponent<Health>(entity);
if (health->IsAlive == false)
{
Transform3D* transform3D = frame.GetComponent<Transform3D>(entity);
Destroy(frame, entity, transform3D->Position, transform3D->Rotation);
}
}
private void Destroy(Frame frame, EntityRef entity, FPVector3 position, FPQuaternion rotation)
{
EntityRef ownerEntity = frame.TryGetOwnerEntity(entity);
// 2. Spawn effects
foreach (DestroyEffect effect in DestroyEffects)
{
frame.CreateEntity(effect.EntityPrototype, ownerEntity, position + effect.PositionOffset, rotation);
}
// 3. Invoke event and destroy entity
frame.Events.DestructibleObjectDestroyed(entity);
frame.DestroyEntity(entity);
}
Health Area
C#
public unsafe class HealthAreaController : UpdatableActorController, IInteractableTriggerController
{
//========== PUBLIC MEMBERS ===================================================================================
public EHealthAction Action = EHealthAction.Remove;
public EHealthType Type = EHealthType.Health;
public FP HealthPerTick = FP._10;
public FP TickInterval = FP._1;
public bool InitEnabled = true;
public LayerMask TargetLayerMask;
//========== ActorController INTERFACE ========================================================================
internal override void Initialize(Frame frame, EntityRef entity)
{
// 1. Add HealthArea component (support for custom rollbackable data), set initial state
HealthArea* healthArea = frame.TryGetOrAddComponent<HealthArea>(entity);
healthArea->SetEnabled(frame, entity, InitEnabled);
}
//========== UpdatableActorController INTERFACE ===============================================================
internal override void Update(Frame frame, EntityRef entity)
{
HealthArea* healthArea = frame.GetComponent<HealthArea>(entity);
if (healthArea->IsEnabled == false)
return;
// 2. Health action ticks in intervals
healthArea->TickDelay -= frame.DeltaTime;
if (healthArea->TickDelay <= FP._0)
{
Tick(frame, entity);
healthArea->TickDelay += TickInterval;
}
}
//========== IInteractableTriggerController INTERFACE ===================================================================
bool IInteractableTriggerController.OnEnter(Frame frame, EntityRef entity, EntityRef instigator, TriggerInfo3D info)
{
// 3. Only entities with Health component and valid layer are tracked
if (frame.HasComponent<Health>(instigator) == false)
return false;
if (info.Other.HasValidLayer(frame, TargetLayerMask) == false)
return false;
return true;
}
void IInteractableTriggerController.OnExit(Frame frame, EntityRef entity, EntityRef instigator, ExitInfo3D info)
{
}
//========== PRIVATE METHODS ==================================================================================
private void Tick(Frame frame, EntityRef entity)
{
InteractableTrigger* interactableTrigger = frame.GetComponent<InteractableTrigger>(entity);
QList<EntityRef> instigators = frame.ResolveList(interactableTrigger->Instigators);
if (instigators.Count == 0)
return;
// 4. Prepare hit and health data
HitData hitData = new HitData()
{
Source = entity,
Instigator = entity,
BodyPartType = EBodyPartType.None,
HitSource = EHitSource.Environment,
};
HealthData healthData = new HealthData
{
Action = Action,
DesiredAmount = HealthPerTick,
Source = entity,
Instigator = entity,
Type = Type,
};
// 5. Iterate over all instigators (allowed entities) and hit them if they are alive
for (int i = instigators.Count; i --> 0;)
{
EntityRef instigator = instigators[i];
if (frame.DestroyPending(instigator) == true)
continue;
Health* health = frame.GetComponent<Health>(instigator);
if (health->IsAlive == false)
continue;
hitData.Target = instigator;
hitData.Position = frame.GetComponent<Transform3D>(instigator)->Position;
healthData.Target = instigator;
frame.Signals.Hit(hitData, healthData);
}
}
}
Damage Area
It is possible to turn the Health Area shown in the previous section into a Damage Area by tweaking the properties to subtract health and shield rather than add to it.
Creating a New Actor
This is the step-by-step procedure to create a new Actor in the FPS Template. The steps are identical for both player actors and AI actors.
- In the Quantum solution, create a new
MyActorController
which inherits fromActorController
: for static actors; or,UpdatableActorController
: for dynamic actors with update every frame.
- Add serializable fields to the controller (configuration)
- If needed, create a
MyActor.qtn
containing the definition for theMyActor
component and other data structures such as roll-backable data. - Implement the
Initialize()
,Deinitialize()
andUpdate()
methods in the controller. - Implement the available controller interfaces (e.g.:
IInputController
). - Recompile the
quantum.code
solution. - In Unity, create an empty GameObject and add the visual components / model as a child object.
- Add the 3 core scripts
Entity
,EntityPrototype
andEntityComponentController
to the GameObject. - On the
Entity
component, setTransform Synchronization
toNone
: if the position / rotation never changes after spawnSimple
: if the position / rotation does not need to be interpolatedInterpolate
/Error Correction
: for smooth position / rotation synchronization
- On the
EntityPrototype
component, setTransform
to 3D - On the
EntityComponentController
component, selectMyActorController
in theActorController
drop-down menu and fill out the defined properties. - Add other entity components to define the entity's behavior (
EntityComponentMyActor
,EntityComponentHealth
,EntityComponentPhysicsCollider3D
, etc...). - Create a prefab of the entity (Optional).
- Execute the asset resource generation via the
Quantum > Generate Asset Resources
menu. - The entity is now ready to use - drag and drop it into scene, reference the entity prototype to other entity fields.