Creating a FNAF Jump Scare
In this video, we bring the FNAF jump scares to life by creating fully animated animatronic attacks. We’ll walk through two different approaches: frame-by-frame image swapping using pre-rendered images and pivot-based sprite animation for more dynamic movement. You’ll also learn how to add that iconic scream audio, and give your background a camera-shake effect for maximum impact.
This is the third episode in our series where we build a complete 2D FNAF-style game from scratch using real programming and logic — no shortcuts, just solid Unity skills.
You can copy and paste the complete code directly from the tutorial or…
Just want the Unity Package with everything pre-built? Become a Patreon member at the Creator Level to download the entire Unity package:
Sprite Hierarchy
The ThinRabbit_Georgie prefab is built as a fully articulated 2D sprite character, where each major body part is a separate child object. This setup allows for realistic movement by rotating parts around carefully placed pivot points. The torso pivots at the waist, arms pivot at the shoulders and elbows, and the jaw pivots at the hinge of the mouth. By breaking the character into these components, you can animate the rabbit lunging, swinging its arms, tilting its head, and snapping its jaw.
This modular approach makes animations flexible and reusable. Because each piece moves independently, you can layer motions for more dynamic and intense jump scares. The pivot-based hierarchy also makes tweaking animations simple, since changes to one limb or joint won’t affect the rest of the model.
JumpScareSequence.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class JumpScareSequence : MonoBehaviour
{
public RawImage scareImage;
public Texture2D[] frames;
public float frameRate = 30.0f;
public int repeat = 1;
public Animator camera_shake;
private AudioSource audioSource;
private bool hasPlayed = false;
public void Start()
{
audioSource = GetComponent();
}
public void Play()
{
if (!hasPlayed)
{
hasPlayed = true;
StartCoroutine(PlaySequence());
}
}
private IEnumerator PlaySequence()
{
scareImage.gameObject.SetActive(true);
scareImage.transform.SetAsLastSibling();
float delay = 1.0f / frameRate;
audioSource.Play();
camera_shake.SetTrigger("Shake");
for (int i = 0; i < repeat; i++)
{
foreach (Texture2D frame in frames)
{
scareImage.texture = frame;
yield return new WaitForSeconds(delay);
}
}
hasPlayed = false;
scareImage.gameObject.SetActive(false);
}
}
JumpScareAnimation.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using static TMPro.SpriteAssetUtilities.TexturePacker_JsonArray;
using UnityEngine.UIElements;
public class JumpScareAnimation : MonoBehaviour
{
public Animator camera_shake;
public UnityEngine.UI.Image puppet;
private Animator animator;
private AudioSource audioSource;
private bool hasPlayed = false;
public void Start()
{
audioSource = GetComponent();
animator = GetComponent();
}
public void Play()
{
if (!hasPlayed)
{
StartCoroutine(PlaySequence());
}
}
private IEnumerator PlaySequence()
{
puppet.gameObject.SetActive(true);
hasPlayed = true;
audioSource.Play();
camera_shake.SetTrigger("Shake");
animator.SetTrigger("JumpScare");
yield return null; //Wait for the animator to be updated.
AnimatorStateInfo info = animator.GetCurrentAnimatorStateInfo(0);
yield return new WaitForSeconds(info.length);
hasPlayed = false;
puppet.gameObject.SetActive(false);
}
}