One of the first major challenges I encountered in this project was unexpected latency and jitter in the animation feedback inside Unreal Engine. At the beginning, this was frustrating because the visual behaviour looked like a hiccup—small recursive jumps in the animation, happening regularly. After investigating the issue end-to-end, I now understand much more clearly how timing, data pacing, and sensor noise interact between the physical world (Arduino) and the virtual world (Unreal Engine).
This section describes what caused the latency, why delay() and millis() behave differently, and how I ultimately fixed the issue.
1. Two Different Worlds With Two Different Clocks
The core issue comes from the fact that Arduino and Unreal Engine operate on completely separate update loops:
- Arduino sends sensor data at whatever pace your sketch defines.
- Unreal Engine reads the serial port at the pace defined in Blueprints (e.g., using a timer running every X milliseconds).
If these two paces do not match, Unreal will sometimes read:
- half-written lines,
- empty strings, or
- strings that cut off mid-value,
which produces 0, truncated values, or invalid numeric conversions.
This is exactly what caused the visible jitter in animation.
2. Why delay() Causes Problems
In my early implementation, I used Arduino’s built-in delay() function to control the output frequency.
What delay() actually does
delay(100) literally freezes the entire microcontroller for 100 milliseconds.
During that freeze:
- no sensor updates occur
- no serial writes occur
- capacitive updates pause
- the device cannot respond to anything
This creates blocking behaviour and interrupts the natural flow of data.
When Unreal tries to read the serial port during one of these blocked periods, it often receives an incomplete line or nothing at all — which appears as a value of 0 after parsing.
This is why I was seeing irregular spikes and jitter in animation.
3. Why millis() Works Better
millis() allows us to implement non-blocking timing.
Instead of freezing the Arduino, we check whether the required time interval has passed:
unsigned long now = millis();
if (now - lastSendTime >= sendInterval) {
lastSendTime = now;
// send sensor data
}
The advantages:
- Arduino keeps reading sensors continuously
- capacitive updates continue uninterrupted
- serial communication never freezes
- Unreal receives consistent packets
- no backlog or piling up of delayed operations
This is crucial when sending multiple sensor values in one line.
4. Why Unreal Recursion Made the Problem Worse
Originally, I used a recursive function in Unreal to constantly read the serial port.
Inside that recursive call, Unreal Blueprint also contained its own Delay node.
This created a double-latency situation:
- Arduino was delayed
- Unreal was delayed
- Their delays drifted out of sync over time
This explains the “recursive hiccup” behaviour — Unreal repeatedly attempted to read data during moments when Arduino was not sending anything because it was frozen by delay().
The result:
empty strings → parsed as zero → visual jitter in animation.
Fix
I replaced the recursion with Unreal’s:
✔ Set Timer by Function Name
✔ consistent “read every X ms” behaviour
✔ no recursion
✔ no stack buildup
✔ consistent pacing
This matched Arduino’s non-blocking millis() timing and removed the jitter.
5. Why Smoothing the Data Is Essential
Another problem was that analogue sensors (stretch sensors, LDRs, proximity deltas) are naturally noisy.
Unfiltered raw data is unstable:
- small spikes
- sudden drops
- inconsistent readings
Using raw input for animation blending is especially problematic — blend spaces expect gradual transitions, not random jumps.
Solution: Smoothing
On Arduino, we can smooth each sensor individually before sending:
- Moving average
- Exponential smoothing
- Median filtering
- Sample averaging
This reduces noise drastically and produces stable, predictable curves for animation.
6. Guaranteeing Correct Data Parsing in Unreal
Because I send 5 sensor values in every line, Unreal must ensure it reads a complete line, not a partial one.
My message format:
touch, toggle, proximity, stretch, LDR
To prevent Unreal from parsing incomplete packets, I check:
✔ ArrayLength == 5
Only if the incoming line splits into exactly 5 values do I allow animation logic to run.
If not, that frame is skipped.
This eliminates crashes and unexpected behaviour caused by truncated serial messages.
Summary
Why the jitter appeared:
- Arduino froze during
delay()→ incomplete serial messages - Unreal read during these frozen periods → empty strings
- Unreal recursion amplified timing drift
- raw analogue data introduced noise
What fixed it:
- replacing
delay()withmillis()(non-blocking timing) - using Unreal’s timer instead of recursive calls
- smoothing sensor data before sending
- verifying
ArrayLength == 5before parsing
Result:
Smooth, stable, responsive interaction between physical sensors and Unreal animation.