Skip to content
/a/ FaceCue Performance Studio

Look Target

What the character looks at.

Setting it is how a character looks at anything, and both gaze engines take it the same way.

The Source

Implement ILookTargetSource and assign it to the brain's LookTargetSource.

Member What it returns
Transform CurrentTarget { get; } Where the character should be looking, or null for nothing
bool CurrentTargetVisible => true; Whether the target can currently be seen. It answers true unless you implement it, so a source that has no notion of visibility can leave it out
FaceCueCharacter CurrentTargetCharacter => null; Optional. Another FaceCue character to look at. When it names one, it is used instead of CurrentTarget. See Looking at a Character
LookAtPart CurrentTargetPart => LookAtPart.Eyes; Optional. Which part of that character to look at

FaceCue polls it. A source that follows the player, the nearest speaker, or whatever your own attention system decides is the steady answer, and an override sits on top of it for the exceptions.

Returning null means there is nothing for the character to look at right now. The eyes go back to the character's own resting gaze, the small glances and drift it makes whenever nothing holds its attention, and the head eases back onto your animation. See At Rest.

A source never has to say whether it is active. Returning null is enough. So if the head is shared between FaceCue, an animator and another look-at system, the source returns null whenever it is not FaceCue's turn, and a target again when it is.

What Visible Means

CurrentTargetVisible is the one member most sources never touch, and it is worth knowing what it does and does not do.

It is the character's perception, not its intent. The look target is what the character means to look at. Visible is whether it can actually see that thing right now. While it is false, the character keeps following where it expects the target to be instead of freezing, the way a person keeps following something that has passed behind cover, until it eventually gives up. When it turns true again, the look catches up with where the target really is, with a real movement if it turned out somewhere else. Eyes & Gaze has the worked example, a runner passing behind a van.

It is not a way to stop looking. Returning null from CurrentTarget releases the look at once, whatever this member says, and switching tracking on and off is tracking permission, not this. Use it for what it names: the target is still the target, the character just cannot see it right now.

Your game decides it. A line-of-sight check, a trigger volume, whatever you already know about occlusion. FaceCue never computes visibility itself, which is why the default is true.

Only the Biomechanical engine reads it. Simple aims at the target it is given, so under Simple this member changes nothing.

Only a source can say it. An override is a bare Transform with no visibility channel, so an override target always reads as visible. If a target your game sets by override can pass behind cover, put it on a source instead, where the flag is yours to drive, and keep the override for the exceptions that are always in view.

Telling the Character Where Something Is

There is a second way in, beside the source and the override, for a moment rather than a target: NoticeLookTarget on the Eyes driver. It tells the character where its look target is right now, as an event from your game, a squad callout, a scripted glance, a director's cue. It updates where the character believes the target is, even while the target is marked not visible, and it wakes a look that had given up.

It is how the other senses reach the look. Visible suits a steady signal, what the character can see from moment to moment. A sound, a tremor through the floor, a shout from a squadmate, or anything else your game can detect tends to arrive as a moment instead, and this is the way to hand that over: the character learns where its target is without needing to see it.

It is on the Eyes driver, in two forms:

public void NoticeLookTarget(Vector3 worldPosition)
public void NoticeLookTarget(Vector3 worldPosition, float addressIntent01)
Parameter What it does
worldPosition Where the character should now believe its look target is, in world space
addressIntent01 How much the character addresses the target for this look, from 0, a glance, to 1, a full turn of the body
var eyes = GetComponent<EyesDriver>();
eyes.NoticeLookTarget(worldPosition);
eyes.NoticeLookTarget(worldPosition, addressIntent01: 1f);   // and face it fully

It is a belief, not a target. The character keeps looking at whatever the source or override supplies, and the notice only says where that thing is believed to be. The second form also sets how much the character addresses the target for this look, from a glance to a full turn of the body, overriding the driver's Address Intent until the look changes. See The Trunk. Both forms are Biomechanical only, and do nothing under Simple.

Too Close

A target that comes right up to the face would cross the eyes and make them flicker, so the Eyes driver carries a Minimum Tracking Distance in metres. Inside it the character still has the target and still counts as tracking it, it just stops chasing it with the eyes, and whatever else is switched on, the head, the body, the small movements, carries the look instead. Inside that distance the target must also sit in front of the face to be aimed at, so a target pulled onto the head itself is not chased round behind the eyes.

Beyond Reach

A target further round than eyes, neck, head and trunk can reach together is held at the edge of reach, and the body has to turn to bring it back. FaceCue does not turn the body. It tells you.

With Request A Body Turn on, the Eyes driver raises BodyReorientRequested when a target is found to be beyond total reach, and again if the character lost track of that target and then catches sight of it again, with a BodyReorientCue carrying how far the body would have to turn:

Field Holds
SignedYawDeficitDeg How far the body would need to turn to bring the target back into reach, right-positive
SignedElevationDeficitDeg The same, up and down
AzimuthDeg Where the target is, as a bearing from the character's current facing
GazeAmplitudeDeg The whole angle from body-forward to the target

This is where FaceCue's part ends and your game's begins. Hook the event to your character controller or animation system, and let that decide whether the character turns, steps or stays where it is. The driver only reports, so a character with nothing subscribed, or with the toggle off, simply settles at its reach, which is also what a person does when they cannot be bothered to turn round. Turn by the reported deficit and the target lands back inside reach, and the request closes on its own.

Start From the Example

Two scripts ship under Examples, and together they show the whole lane. SimpleLookTargetSource satisfies the source with one Transform and one toggle, and its header lists the ways a real project usually grows past it: picking targets from a detection zone, taking the toggle from an arbitrator, and adding a little hysteresis so a brief gap between targets does not flicker the eyes. SimpleLookTargetSetter drives the override, on both the anonymous and the owned lane, with the guarded release.

The setter also shows the one thing that is true of this lane and no other: setting the target to nothing and releasing the override are the same act. A number or a toggle has a real zero worth imposing on a character. A look does not, so there is no empty target to hand over, only handing the eyes back.

Setting an Override

brain.SetLookTarget(target);          // anonymous lane
brain.SetLookTarget(this, target);    // as this owner
Member What it does
void SetLookTarget(Transform target) Claims the anonymous lane
void SetLookTarget(object owner, Transform target) Claims the lane as this owner
bool ReleaseLookTargetOverride() Gives back the anonymous lane. True when there was one to give back
bool TryReleaseLookTargetOverride(object owner) Gives the lane back only if this owner still holds it. True when it did
bool ForceClearLookTargetOverride() Takes the lane back whoever holds it. True when there was an override to clear

Null Hands the Look Back to the Source

Passing null to SetLookTarget does not make the character look at nothing. It releases your override, the same as ReleaseLookTargetOverride(), and the character goes back to whatever its source offers. Sources & Overrides shows how the two layers fit.

Say a guard's source keeps offering the player, and a cutscene needs the guard to stop watching them. SetLookTarget(door) turns the guard to the door. SetLookTarget(null) gives the look straight back to the source, and the guard turns back to the player.

To stop following a target the source keeps offering, either change what the source returns, or turn off tracking for as long as you need. Tracking keeps the target while it is off, so turning it back on picks the same look up again.

Looking at a Character

When the target is another FaceCue character, name the character rather than one of its transforms, and pick the part to look at. The character then knows whom it is looking at, and FaceCue finds that part on the other character's own rig and follows it as it moves.

brain.SetLookTargetCharacter(captain);                    // the captain's eyes
brain.SetLookTargetCharacter(captain, LookAtPart.Mouth);  // a little below them
brain.SetLookTargetCharacter(this, captain);              // as this owner
Member What it does
void SetLookTargetCharacter(FaceCueCharacter character, LookAtPart part = LookAtPart.Eyes) Claims the anonymous lane, looking at that character
void SetLookTargetCharacter(object owner, FaceCueCharacter character, LookAtPart part = LookAtPart.Eyes) Claims the lane as this owner
LookAtPart Where the look lands
Eyes The character's eyes, the ordinary place to look at someone
Mouth The character's mouth
Body The character's chest, a look at the person rather than the face

It is the same lane as SetLookTarget: a look at a character replaces a look at a transform and the reverse, the same three calls give it back, and null hands the look back to the source. A FaceCueCharacter is what other characters are named by, and a character's brain is one, so pass the other character's FaceCueBrain.

A character that is disabled is still looked at, where it last stood. One that is destroyed gives the look back to the source. A character cannot look at itself, and that call is refused with a warning.

The Speaker Context and the Listener Context both offer SetLookTargetCharacter, the three releases and the two reads below, so code holding either needs nothing else.

Following a Character's Part Yourself

The point a look aims at is one your own code can follow too: a camera's look-at target, an aim constraint, a distance check. Every FaceCue character keeps one on each part someone asks for, and every caller gets the same one, a character looking at that part included. So a camera framing the captain's eyes and a guard looking the captain in the eye always agree.

Member What it does
Transform GetLookAtPartTransform(LookAtPart part) The transform FaceCue keeps on that part of the character. The same transform every time, for as long as the character exists. FaceCue places it, so leave it where it is
Vector3 GetLookAtPartPosition(LookAtPart part) Where that part is, read from the same transform
// hand the captain's eyes to your camera as the target it keeps in frame
cameraTarget = captain.GetLookAtPartTransform(LookAtPart.Eyes);

// is the guard close enough to whisper to the captain?
float distance = Vector3.Distance(
    guard.GetLookAtPartPosition(LookAtPart.Mouth),
    captain.GetLookAtPartPosition(LookAtPart.Eyes));

FaceCue moves the point every frame to where the part is drawn, so you can read it whenever you need it, in Update, LateUpdate or your camera system's own update, and a camera that reads it every frame follows smoothly on any setting. A read in your own LateUpdate gets where the part was on the previous frame, since FaceCue finishes the character after it. For the exact point the frame is drawn with, read it in an OnPostApply handler, one of the two places to work with FaceCue. While a character is disabled, its points stay where it was last drawn.

Asking About It

Member Tells you
Transform CurrentLookTarget { get; } What the character is looking at, override or source. While it looks at a character, the point that character keeps on the chosen part, the same transform its GetLookAtPartTransform returns
FaceCueCharacter LookTargetCharacter { get; } The character it is looking at, or null for a plain transform or nothing
LookAtPart LookTargetPart { get; } Which part of that character. Eyes when it names no character
bool CurrentLookTargetVisible { get; } Whether that target is visible
bool IsLookTargetOverridePresent { get; } Whether an override is masking the source
bool IsLookTargetOverrideOwner(object owner) Whether this owner holds it
object LookTargetOverrideOwner { get; } Who holds it
InputOverrideOwnerKind LookTargetOverrideOwnerKind { get; } None, Anonymous or Explicit

Tracking

Two members on this lane are about whether looking is permitted, not where.

bool GazeTargetTrackingEnabled { get; set; } is the permission. Turn it off and the character stops following its target without forgetting what the target is, which is what you want for a cutscene that has taken the head.

bool IsActivelyTracking { get; } reports whether a target is currently acquired. A target closer than the minimum distance still counts as acquired: the minimum-distance rule changes how the target is followed, not whether it is being tracked.

Events

event Action<LookTargetOverrideChange> LookTargetOverrideChanged fires when the override changes. Its LookTargetOverrideChange carries:

Field Holds
InputOverrideTransitionKind Transition Which kind of change. See Sources and Overrides
bool HadPreviousOverride Whether an override was present before
Transform PreviousTarget The target it was overriding to before
InputOverrideOwnerKind PreviousOwnerKind How it was held before: None, Anonymous or Explicit
object PreviousOwner Who held it before. On a Vacated change this is a destroyed object: compare it, never use it
bool HasCurrentOverride Whether an override is present now
Transform CurrentTarget The target it overrides to now
InputOverrideOwnerKind CurrentOwnerKind How it is held now
object CurrentOwner Who holds it now
FaceCueCharacter PreviousCharacter The character it looked at before, or null. It may be destroyed: compare it, never use it
LookAtPart PreviousPart The part it looked at before
FaceCueCharacter CurrentCharacter The character it looks at now, or null
LookAtPart CurrentPart The part it looks at now

PreviousTarget and CurrentTarget are null for a look at a character.

event Action<bool, bool> GazeTargetTrackingEnabledChanged fires when tracking permission changes, with the previous value and then the current one. Writing the value it already holds publishes nothing, and source polling never raises it, so it reports deliberate permission changes only.

A New Target Always Takes

A new target always replaces the previous one, even when both have since been destroyed.

Where It Reaches

Straight into Gaze, whichever engine is running. How the character gets there, how much of the body joins in, and what happens to a target it cannot comfortably reach are all on that page.

Where a target is read is settled once for everyone: where it was drawn on the previous frame, after everything in the scene has moved, so a target on another FaceCue character is read where that character's face is drawn. When the Target Is Read below has the detail, and the two Expert rows that change it.

When the Target Is Read

A look target is read where it was drawn on the previous frame: once a frame, after everything in the scene has moved, the same for every character whatever order they update in and whichever Solve Schedule each one runs. So two FaceCue characters looking at each other's eyes both land where the other's face is drawn, and a target your own script moves trails the look by one frame, a small smooth lag the eyes' own following covers.