Dot Product, Angle, and Facing Checks¶
At a glance
Lives in: Kismet Math Library; Blueprint API / Math / Vector -
Returns: one real number; never an Actor, Boolean, direction, or None -
Changes: nothing; Dot Product is a pure calculation -
Fails by: returning a mathematically valid but misleading number when
inputs are not normalized for an angle test, use different coordinate
spaces, or include a zero direction -
Official docs: Dot Product,
Get Forward Vector,
and Acos (Degrees)
The one-minute version¶
Dot Productcompares two vectors and returns one scalar.- With two normalized directions, the result is from
-1to1:1means same direction,0means perpendicular, and-1means opposite. - For a front/behind check, dot the source's forward direction with the normalized direction from source to target. Positive is in the forward half-space; negative is behind.
- For a field-of-view cone, compare the dot result with
cos(HalfAngle). A 90-degree full cone uses a 45-degree half-angle and a threshold near0.707. - Normalize before an angle or cone test. Without normalization, vector magnitude changes the result.
- Guard a zero source-to-target vector. Normalizing it returns zero, and a dot of zero can masquerade as a 90-degree angle.
- Use
Acos (Degrees)when the graph needs a human-facing angle in degrees.Acos (Radians)returns radians. Clamp the dot to-1..1first. - Dot product does not test range, walls, visibility, teams, or perception memory. Add distance/trace checks or use AI Perception for those jobs.
What it actually calculates¶
For vectors A and B:
Dot(A, B) = A.X*B.X + A.Y*B.Y + A.Z*B.Z
The geometric meaning is:
Dot(A, B) = Length(A) * Length(B) * cos(angle between A and B)
That second form explains why normalized inputs matter. If both vectors have
length 1, their lengths drop out and the dot equals the cosine of the angle:
| Normalized dot | Approximate angle | Meaning |
|---|---|---|
1.0 |
0 degrees |
same direction |
0.707 |
45 degrees |
diagonal |
0.0 |
90 degrees |
perpendicular |
-0.707 |
135 degrees |
mostly opposite |
-1.0 |
180 degrees |
opposite direction |
The node has no execution pins and no failure branch. It does not normalize its inputs, rotate either vector, or remember previous results.
Normalized and non-normalized inputs¶
Normalize inputs when the result is supposed to mean angle or alignment:
Forward = Get Actor Forward Vector
ToTarget = Normalize(TargetLocation - ActorLocation)
FacingDot = Dot Product(Forward, ToTarget)
Get Actor Forward Vector is already unit length under an ordinary valid
transform. The subtraction is an offset with distance still inside it, so that
input must be normalized.
Non-normalized dot products are not wrong. They answer a different question. For example, dotting velocity with a unit forward vector gives signed forward speed:
ForwardSpeed = Dot Product(Get Velocity, Get Actor Forward Vector)
- positive: moving partly forward;
- zero: no forward/back component;
- negative: moving partly backward.
Dotting the same velocity with the Actor's right vector gives signed lateral speed. This is useful for directional locomotion because magnitude is intentional there.
See Vector Length / Vector Length XY / Normalize for safe normalization and the near-zero fallback.
Front, behind, and a field-of-view cone¶
Front or behind¶
Offset = TargetLocation - SourceLocation
if Vector Length Squared(Offset) <= MinimumDistance * MinimumDistance:
target is at the source; handle that case explicitly
else:
ToTarget = Normalize(Offset)
FacingDot = Dot Product(SourceForward, ToTarget)
FacingDot > 0 -> target is in the forward half-space
FacingDot = 0 -> target is approximately sideways
FacingDot < 0 -> target is in the rear half-space
In gameplay, compare with a small threshold rather than exact equality with zero. Floating-point results near a boundary can vary by tiny amounts.
A narrower cone¶
A field of view is usually described as the full angle from one edge to the other. Dot product compares one direction with the cone's center line, so the threshold uses half that angle:
HalfAngle = FullFieldOfViewDegrees * 0.5
Threshold = Cos (Degrees)(HalfAngle)
InsideCone = FacingDot >= Threshold
Common thresholds:
| Full cone | Half-angle | Dot threshold |
|---|---|---|
60 degrees |
30 degrees |
about 0.866 |
90 degrees |
45 degrees |
about 0.707 |
120 degrees |
60 degrees |
0.5 |
180 degrees |
90 degrees |
0 |
A higher threshold means a narrower forward cone. Store either the designer's angle and calculate the threshold when it changes, or expose the threshold with a clear name. Recalculating a constant cosine on every Tick is harmless at small scale but unnecessary.
Getting the actual angle with Acos¶
If all you need is an inside/outside test, compare the dot directly. Acos is
extra work and makes the graph harder to read.
When the actual angle is needed:
DirectionA = Normalize(A)
DirectionB = Normalize(B)
Dot = Dot Product(DirectionA, DirectionB)
SafeDot = Clamp(Dot, -1, 1)
Angle = Acos (Degrees)(SafeDot)
Clamp first because floating-point calculations can produce a value just past
1 or -1, outside inverse cosine's valid domain.
Current Blueprint search exposes the unit in the node name:
Acos (Degrees)returns degrees;Acos (Radians)returns radians.
Do not feed a radians result into a pin documented in degrees, or convert an
already-degree result a second time. C++ FMath::Acos returns radians, so C++
usually wraps it in FMath::RadiansToDegrees for a degree result.
2D or 3D facing?¶
The standard vector dot uses X, Y, and Z. That is right for flight, turret aim, or a character that must look up and down at a target.
For a ground-facing check, remove vertical influence from both directions before normalizing:
FlatForward = (SourceForward.X, SourceForward.Y, 0)
FlatOffset = (Target.X - Source.X, Target.Y - Source.Y, 0)
if both flat vectors are safely nonzero:
FlatFacingDot = Dot Product(
Normalize(FlatForward),
Normalize(FlatOffset))
Flattening only one input mixes two different questions. Also guard the rare case where a direction points almost straight up: its flattened version has no reliable heading.
Use 3D for an aerial enemy's vision. Use 2D when height should not make a ground enemy think a nearby player is outside its horizontal facing cone.
Left or right is a different comparison¶
Forward dot tells front versus back. A result of zero only says "perpendicular"; it cannot distinguish left from right.
Use the source's right direction for a left/right test:
ToTarget = Normalize(TargetLocation - SourceLocation)
SideDot = Dot Product(SourceRightVector, ToTarget)
SideDot > 0 -> target is to the source's right
SideDot < 0 -> target is to the source's left
SideDot = 0 -> target lies on the forward/back center line
In Unreal, local X+ is forward and local Y+ is right. Get right from the same Actor, component, or rotation that supplied forward. Mixing body forward with camera right creates a comparison against two different frames.
A cross product can also produce a signed side result, but its sign depends on axis order and the chosen up axis. Right-vector dot is usually clearer in a beginner Blueprint.
Keep both vectors in the same space¶
Dot product only multiplies numbers. It cannot detect that one direction is in world space while another is local to a component.
Valid world-space comparison:
WorldForward = SourceComponent.GetForwardVector
WorldToTarget = Normalize(TargetWorldLocation - SourceWorldLocation)
Dot Product(WorldForward, WorldToTarget)
Valid local-space comparison:
LocalToTarget = Inverse Transform Direction(SourceTransform, WorldToTarget)
compare LocalToTarget with local forward (1, 0, 0)
Do not dot world forward with local target direction. The result may look plausible at one rotation and fail as soon as the source turns. The Break/Make and transform-location page covers local/world conversions in detail.
A facing check is not sight¶
A dot test answers an angular question. A complete visibility decision can need several independent checks:
1. Is target within maximum distance?
2. Is target inside the facing cone?
3. Does a line trace reach the intended target without a wall blocking it?
4. Is the target eligible by team, state, or gameplay rules?
Use Line Trace By Channel for one immediate occlusion query. A trace by itself is not a cone: it tests one line. Use Sphere, Capsule, and Multi Trace basics to gather candidates, then apply range/facing rules if that matches the mechanic.
Pawn Sensing and AI Perception own the larger AI sensing path: sight radius/cone configuration, stimuli, hearing, memory, forgetting, affiliation, and debugger support. A custom dot test is useful for a focused attack arc or interaction prompt, but it does not replace that system automatically.
Debugging a facing check¶
Print or watch these values separately:
- source world location;
- target world location;
- source forward and right vectors;
- normalized direction to target;
- dot result and threshold;
- 2D or 3D mode;
- trace hit Actor, if occlusion matters.
Draw debug lines for forward and to-target directions. If the numbers are correct but the mechanic still fires through walls, the missing piece is the trace, not another dot operation. See Breakpoints and watch values for inspecting the live graph instance.
The C++ twin¶
For the curious, this original example performs a flat 90-degree full-cone test:
bool AGuardCharacter::IsTargetInFrontCone(const AActor* Target) const
{
if (!IsValid(Target))
{
return false;
}
FVector FlatForward = GetActorForwardVector();
FlatForward.Z = 0.0;
FVector FlatOffset = Target->GetActorLocation() - GetActorLocation();
FlatOffset.Z = 0.0;
if (FlatForward.IsNearlyZero() || FlatOffset.IsNearlyZero())
{
return false;
}
const double FacingDot = FVector::DotProduct(
FlatForward.GetSafeNormal(),
FlatOffset.GetSafeNormal());
constexpr double HalfAngleDegrees = 45.0;
const double Threshold = FMath::Cos(
FMath::DegreesToRadians(HalfAngleDegrees));
return FacingDot >= Threshold;
}
Range and line-of-sight checks would remain separate conditions.
Lookalikes - which one do I want?¶
| Tool | Returns/does | Use when | Watch out |
|---|---|---|---|
| Dot Product | Scalar alignment/component | Front/behind, cones, or signed speed. | Normalize for angle meaning. |
| Acos (Degrees) | Angle from a cosine value | You need the actual angle. | Clamp to -1..1; costs more than a threshold. |
| Cross Product | Vector perpendicular to two inputs | You need an axis/normal or advanced signed-angle work. | Input order changes direction. |
| Find Look at Rotation | Rotator from one point toward another | Something should face the target. | It calculates rotation, not visibility. |
| Line Trace By Channel | Blocking-hit query along one segment | Walls or precise line of sight matter. | One ray is not a field-of-view cone. |
| AI Perception | Sensing events and remembered stimuli | AI needs configurable sight/hearing and memory. | Requires listener/sense/stimulus setup. |
Going deeper¶
- Get Forward Vector & Find Look at Rotation - choosing actor, control, camera, or component direction.
- Vector Length / Vector Length XY / Normalize - safe directions, distances, and squared thresholds.
- World vs relative transforms - why world and local values cannot be mixed.
- Seeing the player: Pawn Sensing & AI Perception - the persistent AI sensing boundary.
- Official docs: Dot Product, Get Forward Vector, Acos (Degrees), and Acos (Radians).
- Engine source (requires engine access - we do not reproduce it here):
Kismet vector and trigonometry wrappers are declared in
Engine/Source/Runtime/Engine/Classes/Kismet/KismetMathLibrary.hand implemented inEngine/Source/Runtime/Engine/Private/KismetMathLibrary.cpp; core operations such asTVector::DotProductlive inEngine/Source/Runtime/Core/Public/Math/Vector.h.