Continued working on the lights, set up playable version and turn trap at the end of the stairs. Working on making stairs feel longer.

This commit is contained in:
2025-12-03 17:23:18 +00:00
parent 732a2577c2
commit e0c93be280
67 changed files with 29354 additions and 534 deletions

View File

@@ -0,0 +1,82 @@
using UnityEngine;
public class DoorController : MonoBehaviour, IInteractable
{
[Header("Movement Settings")]
public float openAngle = 90f; // How far the door swings (usually 90 or -90)
public float smoothSpeed = 2f; // How fast it opens
private bool isOpen = false;
[Header("Horror Mechanics")]
[Range(0f, 1f)] public float stuckChance = 0.1f; // 10% chance to fail
public float rattleStrength = 5f; // How much it shakes if stuck
[Header("Audio")]
public AudioSource audioSource;
public AudioClip openSound;
public AudioClip closeSound;
public AudioClip stuckSound; // Sound of handle jiggling or locked thud
// Internal State
private Quaternion initialRotation;
private Quaternion targetRotation;
void Start()
{
// Remember how the door was rotated at the start
initialRotation = transform.localRotation;
targetRotation = initialRotation;
// Auto-fetch audio source if missed
if (audioSource == null) audioSource = GetComponent<AudioSource>();
}
void Update()
{
// Smoothly rotate the door towards the target rotation every frame
transform.localRotation = Quaternion.Slerp(transform.localRotation, targetRotation, Time.deltaTime * smoothSpeed);
}
public void Interact()
{
// 1. Calculate "Stuck" Mechanic
// Only checking stuck chance if we are trying to OPEN it (closing usually doesn't jam)
if (!isOpen && Random.value < stuckChance)
{
PlaySound(stuckSound);
// Optional: Add a tiny visual "shake" here if you want advanced polish
return;
}
// 2. Toggle State
isOpen = !isOpen;
// 3. Set Target Rotation
if (isOpen)
{
// Calculate rotation based on the openAngle
targetRotation = initialRotation * Quaternion.Euler(0, openAngle, 0);
PlaySound(openSound);
}
else
{
// Return to start
targetRotation = initialRotation;
PlaySound(closeSound);
}
}
void PlaySound(AudioClip clip)
{
if (audioSource != null && clip != null)
{
audioSource.PlayOneShot(clip);
}
}
public string GetDescription()
{
if (isOpen) return "Press [E] to <color=yellow>Close</color>";
else return "Press [E] to <color=yellow>Open</color>";
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: be04b116a17111d499850e75cbf8180c

View File

@@ -14,13 +14,13 @@ public class GameManager : MonoBehaviour
public void RegisterSwitch(LightSwitch sw)
{
if(!allSwitches.Contains(sw)) allSwitches.Add(sw);
if (!allSwitches.Contains(sw)) allSwitches.Add(sw);
}
public void CheckLights()
{
bool anyLightsOn = false;
foreach(LightSwitch sw in allSwitches)
foreach (LightSwitch sw in allSwitches)
{
if (sw.isLightOn) anyLightsOn = true;
}
@@ -36,7 +36,7 @@ public class GameManager : MonoBehaviour
{
// Find all switches that are currently OFF
List<LightSwitch> offSwitches = new List<LightSwitch>();
foreach(LightSwitch sw in allSwitches)
foreach (LightSwitch sw in allSwitches)
{
if (!sw.isLightOn) offSwitches.Add(sw);
}
@@ -49,4 +49,12 @@ public class GameManager : MonoBehaviour
Debug.Log("A light turned back on!");
}
}
public bool AreAnyLightsOn()
{
foreach (LightSwitch sw in allSwitches)
{
if (sw.isLightOn) return true;
}
return false;
}
}

View File

@@ -0,0 +1,45 @@
using UnityEngine;
public class HauntedPhone : MonoBehaviour
{
public AudioSource phoneAudioSource;
public AudioClip ringSound;
private bool isRinging = false;
void Start()
{
if(phoneAudioSource == null) phoneAudioSource = GetComponent<AudioSource>();
phoneAudioSource.loop = true; // Make sure the ringing loops!
}
// Call this from the LightSwitch "OnFirstSwitchOff"
public void StartRinging()
{
if (!isRinging && ringSound != null)
{
phoneAudioSource.clip = ringSound;
phoneAudioSource.Play();
isRinging = true;
Debug.Log("Phone started ringing...");
}
}
// Call this from the LightSwitch "OnFirstSwitchOn"
public void StopRinging()
{
if (isRinging)
{
phoneAudioSource.Stop();
isRinging = false;
Debug.Log("Phone stopped.");
}
}
// Call this from "OnRandomEvent" for a quick scare
public void PlayCreepyWhisper()
{
// Logic for a one-shot scare sound
Debug.Log("Creepy whisper played from phone...");
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fafa542c827db5641841e841b00e8388

View File

@@ -0,0 +1,82 @@
using UnityEngine;
public class HauntedPram : MonoBehaviour
{
public AudioSource pramAudioSource, babyAudioSource;
public AudioClip creakSound, babyCrySound;
private bool isCreaking = false, isCrying = false;
void Start()
{
if(pramAudioSource == null) pramAudioSource = GetComponent<AudioSource>();
pramAudioSource.loop = true; // Make sure the creaking loops!
}
// Call this from the LightSwitch "OnFirstSwitchOff"
public void StartCreaking()
{
if (!isCreaking && creakSound != null)
{
pramAudioSource.clip = creakSound;
pramAudioSource.Play();
isCreaking = true;
Debug.Log("Pram started creaking...");
}
}
// Call this from the LightSwitch "OnFirstSwitchOn"
public void StopCreaking()
{
if (isCreaking)
{
pramAudioSource.Stop();
isCreaking = false;
Debug.Log("Pram stopped creaking.");
}
}
public void StartCrying()
{
if (!isCrying && babyCrySound != null)
{
pramAudioSource.clip = babyCrySound;
pramAudioSource.Play();
isCrying = true;
Debug.Log("Pram baby started crying...");
}
}
public void StopCrying()
{
if (isCrying)
{
pramAudioSource.Stop();
isCrying = false;
Debug.Log("Pram baby stopped crying.");
}
}
public void StartAllSounds()
{
if (!isCreaking && creakSound != null && !isCrying && babyCrySound != null)
{
pramAudioSource.clip = creakSound;
babyAudioSource.clip = babyCrySound;
pramAudioSource.Play();
babyAudioSource.Play();
isCreaking = true;
isCrying = true;
Debug.Log("Pram started creaking and crying...");
}
}
public void StopAllSounds()
{
if (isCreaking || isCrying)
{
pramAudioSource.Stop();
babyAudioSource.Stop();
isCreaking = false;
isCrying = false;
Debug.Log("Pram stopped all sounds.");
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 677922238343afb44af86e0251cf2bfc

View File

@@ -0,0 +1,50 @@
using UnityEngine;
public class HauntedTV : MonoBehaviour
{
public AudioSource tvAudioSource, tvLaughAudioSource, watcherAudioSource;
public AudioClip tvLaughSound, tvSound, watcherLaughSound;
public Light tvLight;
private bool isOn = false;
void Start()
{
if(tvAudioSource == null) tvAudioSource = GetComponent<AudioSource>();
tvAudioSource.loop = true; // Make sure the static loops!
if(watcherAudioSource == null) watcherAudioSource = GetComponent<AudioSource>();
watcherAudioSource.loop = true; // Make sure the laugh loops!
if (tvLight != null) tvLight.enabled = false;
}
// Call this from the LightSwitch "OnFirstSwitchOff"
public void StartEvent()
{
if (!isOn && tvSound != null && watcherLaughSound != null && tvLaughSound != null)
{
tvAudioSource.clip = tvSound;
tvLaughAudioSource.clip = tvLaughSound;
watcherAudioSource.clip = watcherLaughSound;
tvAudioSource.Play();
tvLaughAudioSource.Play();
watcherAudioSource.Play();
isOn = true;
if (tvLight != null) tvLight.enabled = true;
Debug.Log("TV started static...");
}
}
// Call this from the LightSwitch "OnFirstSwitchOn"
public void StopEvent()
{
if (isOn)
{
tvAudioSource.Stop();
watcherAudioSource.Stop();
tvLaughAudioSource.Stop();
isOn = false;
if (tvLight != null) tvLight.enabled = false;
Debug.Log("TV stopped static.");
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 69e32356c87a2b44f88628ea403dce6c

View File

@@ -0,0 +1,44 @@
using UnityEngine;
public class HeadBobber : MonoBehaviour
{
[Header("Settings")]
public float bobSpeed = 14f; // How fast the head bobs
public float bobAmount = 0.05f; // How high/low the head goes
[Tooltip("Reference to the parent player object")]
public CharacterController playerController;
private float defaultPosY = 0;
private float timer = 0;
void Start()
{
// Remember where the camera is supposed to be normally
defaultPosY = transform.localPosition.y;
}
void Update()
{
if (playerController == null) return;
// Only bob if we are moving roughly walking speed
// (magnitude > 0.1f) means we are moving
if (Mathf.Abs(playerController.velocity.x) > 0.1f || Mathf.Abs(playerController.velocity.z) > 0.1f)
{
// Calculate the Sine Wave
timer += Time.deltaTime * bobSpeed;
float newY = defaultPosY + Mathf.Sin(timer) * bobAmount;
// Apply it
transform.localPosition = new Vector3(transform.localPosition.x, newY, transform.localPosition.z);
}
else
{
// Reset to neutral when stopped
timer = 0;
Vector3 targetPos = new Vector3(transform.localPosition.x, defaultPosY, transform.localPosition.z);
transform.localPosition = Vector3.Lerp(transform.localPosition, targetPos, Time.deltaTime * 5f);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 23762b76f3f99214ebae97c1f93e2d92

View File

@@ -0,0 +1,49 @@
using UnityEngine;
public class InfiniteStairs : MonoBehaviour
{
[Header("Settings")]
[Tooltip("How many times must they climb before the loop breaks?")]
public int loopsRequired = 3;
[Tooltip("Drag an empty GameObject here. Position it lower down the stairs.")]
public Transform resetPoint;
private int currentLoops = 0;
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
// Only loop if we haven't finished the required cycles
if (currentLoops < loopsRequired)
{
// 1. Calculate the offset
// This ensures if they enter the trigger on the left side,
// they appear on the left side at the bottom (smooth transition)
Vector3 offset = other.transform.position - transform.position;
// 2. Teleport
// We keep their Rotation exactly the same, only changing Position
CharacterController cc = other.GetComponent<CharacterController>();
// You must disable CC briefly to teleport, or it sometimes fights back
if (cc != null) cc.enabled = false;
other.transform.position = resetPoint.position + offset;
if (cc != null) cc.enabled = true;
// 3. Increment Loop
currentLoops++;
Debug.Log($"Stair Loop: {currentLoops} / {loopsRequired}");
}
else
{
// Loop finished - Disable this trigger so they can pass
Debug.Log("Loop broken. Player can proceed.");
gameObject.SetActive(false);
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 35a98b200ae5fa14398a2ca7f1ff819e

View File

@@ -1,36 +1,51 @@
using UnityEngine;
using UnityEngine.Events; // Required for the Event System
public class LightSwitch : MonoBehaviour, IInteractable
{
[Header("Settings")]
public Light linkedLight;
public Light[] linkedLight;
public bool isLightOn = true;
[Header("Horror Mechanics")]
[Range(0f, 1f)] public float malfunctionChance = 0.1f;
[Header("Audio")]
public AudioSource audioSource;
public AudioClip switchClickSound;
public AudioClip malfunctionSound;
[Header("Horror Mechanics")]
[Range(0f, 1f)] public float malfunctionChance = 0.1f;
// --- NEW EVENT SYSTEM ---
[Header("Event System")]
[Tooltip("If true, the Special Events below will fire on the first interaction.")]
public bool enableSpecialEvents = false;
[Tooltip("What happens the VERY FIRST time you turn the light OFF? (e.g. Phone Rings)")]
public UnityEvent OnFirstSwitchOff;
[Tooltip("What happens the VERY FIRST time you turn the light ON? (e.g. Phone Stops)")]
public UnityEvent OnFirstSwitchOn;
[Space(10)]
[Range(0f, 100f)] public float subsequentEventChance = 2f; // 2% chance
[Tooltip("What happens randomly after the first time? (e.g. Scary noise)")]
public UnityEvent OnRandomEvent;
// Track how many times we've flipped the switch
private int flipCount = 0;
private bool firstOffTriggered = false;
private bool firstOnTriggered = false;
void Start()
{
// Safety Check: If you forgot to assign the AudioSource, try to find one.
if (audioSource == null) audioSource = GetComponent<AudioSource>();
UpdateLightState();
// Safety Check: Only register if the Manager actually exists
if(GameManager.Instance != null)
{
GameManager.Instance.RegisterSwitch(this);
}
if(GameManager.Instance != null) GameManager.Instance.RegisterSwitch(this);
}
public void Interact()
{
// 1. Calculate Malfunction
// 1. Malfunction Check
if (Random.value < malfunctionChance)
{
PlaySound(malfunctionSound);
@@ -42,14 +57,37 @@ public class LightSwitch : MonoBehaviour, IInteractable
PlaySound(switchClickSound);
UpdateLightState();
// 3. Notify Manager (With Safety Check)
if(GameManager.Instance != null)
if(GameManager.Instance != null) GameManager.Instance.CheckLights();
// 3. HANDLE EVENTS
HandleEvents();
}
void HandleEvents()
{
if (!enableSpecialEvents) return;
// Logic: specific events for first time, random for later
if (!isLightOn && !firstOffTriggered)
{
GameManager.Instance.CheckLights();
// First time turning OFF
OnFirstSwitchOff.Invoke();
firstOffTriggered = true;
}
else
else if (isLightOn && firstOffTriggered && !firstOnTriggered)
{
Debug.LogWarning("Light switched, but no GameManager found to track it!");
// First time turning back ON (after having turned it off)
OnFirstSwitchOn.Invoke();
firstOnTriggered = true;
}
else if (firstOffTriggered && firstOnTriggered)
{
// We have done the scripted part, now roll dice for random scares
float roll = Random.Range(0f, 100f);
if (roll < subsequentEventChance)
{
OnRandomEvent.Invoke();
}
}
}
@@ -62,21 +100,17 @@ public class LightSwitch : MonoBehaviour, IInteractable
void UpdateLightState()
{
foreach(var linkedLight in linkedLight)
if(linkedLight != null) linkedLight.enabled = isLightOn;
}
// Helper function to safely play sounds
void PlaySound(AudioClip clip)
{
if (audioSource != null && clip != null)
{
audioSource.PlayOneShot(clip);
}
if (audioSource != null && clip != null) audioSource.PlayOneShot(clip);
}
public string GetDescription()
{
if (isLightOn) return "Press [E] to turn light <color=red>OFF</color>";
else return "Press [E] to turn light <color=green>ON</color>";
return isLightOn ? "Turn <color=red>OFF</color>" : "Turn <color=green>ON</color>";
}
}

View File

@@ -5,20 +5,29 @@ public class PlayerController : MonoBehaviour
{
[Header("Movement")]
public float moveSpeed = 5f;
public float mouseSensitivity = 15f;
public float gravity = -9.81f; // Standard Earth gravity
public float jumpHeight = 1.0f; // Optional, set to 0 if no jumping allowed
[Header("Look Settings")]
public float mouseSensitivity = 10f;
public float maxLookAngle = 70f;
[Header("Interaction")]
public float interactionDistance = 3f;
public LayerMask interactionLayer;
// Components
private CharacterController controller;
private Transform cameraTransform;
private float verticalRotation = 0f;
// Reference to the generated C# class
// State
private float verticalRotation = 0f;
private Vector3 velocity; // Stores our falling speed
private bool isGrounded; // Are we touching the floor?
// Inputs
private InputSystem_Actions inputActions;
private Vector2 moveInput;
private Vector2 lookInput;
void Awake()
{
@@ -26,24 +35,13 @@ public class PlayerController : MonoBehaviour
controller = GetComponent<CharacterController>();
cameraTransform = Camera.main.transform;
// Lock cursor so it doesn't leave the window
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
// --- INPUT BINDINGS ---
// 1. Movement (WASD)
// Bind Inputs
inputActions.Player.Move.performed += ctx => moveInput = ctx.ReadValue<Vector2>();
inputActions.Player.Move.canceled += ctx => moveInput = Vector2.zero;
// 2. Looking (Mouse)
inputActions.Player.Look.performed += ctx => lookInput = ctx.ReadValue<Vector2>();
inputActions.Player.Look.canceled += ctx => lookInput = Vector2.zero;
// 3. Interaction (The "E" Key)
// This listens strictly for the button press.
// It does NOT trigger on collision.
inputActions.Player.Interact.performed += ctx => TryInteract();
inputActions.Player.Interact.started += ctx => TryInteract();
}
void OnEnable() => inputActions.Enable();
@@ -51,9 +49,28 @@ public class PlayerController : MonoBehaviour
void Update()
{
HandleGravity(); // <--- NEW GRAVITY FUNCTION
HandleMovement();
HandleLook();
UpdateInteractionUI(); // Only updates the text, does not flip switches
UpdateInteractionUI();
}
void HandleGravity()
{
// Check if the controller thinks it's on the ground
isGrounded = controller.isGrounded;
if (isGrounded && velocity.y < 0)
{
// Reset falling speed when on ground (keep slightly negative to stick to floor)
velocity.y = -2f;
}
// Apply Gravity over time
velocity.y += gravity * Time.deltaTime;
// Apply the velocity to the controller
controller.Move(velocity * Time.deltaTime);
}
void HandleMovement()
@@ -62,21 +79,28 @@ public class PlayerController : MonoBehaviour
controller.Move(move * moveSpeed * Time.deltaTime);
}
// ... (Keep HandleLook, UpdateInteractionUI, and TryInteract exactly the same as before)
// For brevity, I am not pasting the repeated Look/Interact code here,
// but ensure you keep those functions in your file!
// If you need the full paste again, let me know.
// ------------------------------------------------------------------------
// PASTE YOUR HandleLook, UpdateInteractionUI, and TryInteract HERE
// ------------------------------------------------------------------------
void HandleLook()
{
float mouseX = lookInput.x * mouseSensitivity * Time.deltaTime;
float mouseY = lookInput.y * mouseSensitivity * Time.deltaTime;
Vector2 rawInput = inputActions.Player.Look.ReadValue<Vector2>();
float mouseX = rawInput.x * mouseSensitivity * Time.deltaTime;
float mouseY = rawInput.y * mouseSensitivity * Time.deltaTime;
verticalRotation -= mouseY;
verticalRotation = Mathf.Clamp(verticalRotation, -90f, 90f);
verticalRotation = Mathf.Clamp(verticalRotation, -maxLookAngle, maxLookAngle);
cameraTransform.localRotation = Quaternion.Euler(verticalRotation, 0f, 0f);
transform.Rotate(Vector3.up * mouseX);
}
// --- INTERACTION LOGIC ---
// This checks what we are looking at to update the screen text (e.g. "Turn On")
void UpdateInteractionUI()
{
Ray ray = new Ray(cameraTransform.position, cameraTransform.forward);
@@ -86,38 +110,23 @@ public class PlayerController : MonoBehaviour
{
IInteractable interactable = hit.collider.GetComponent<IInteractable>();
if (interactable != null && InteractionUI.Instance != null)
{
InteractionUI.Instance.UpdatePrompt(interactable.GetDescription());
}
else if (InteractionUI.Instance != null)
{
InteractionUI.Instance.ClearPrompt();
}
}
else
{
if (InteractionUI.Instance != null) InteractionUI.Instance.ClearPrompt();
}
else if (InteractionUI.Instance != null) InteractionUI.Instance.ClearPrompt();
}
// This is ONLY called when 'E' is pressed.
void TryInteract()
{
Ray ray = new Ray(cameraTransform.position, cameraTransform.forward);
RaycastHit hit;
// Fire a raycast forward
if (Physics.Raycast(ray, out hit, interactionDistance, interactionLayer))
{
// Did we hit something with the IInteractable script?
IInteractable interactable = hit.collider.GetComponent<IInteractable>();
if (interactable != null)
{
// If yes, execute the code (Flip the switch)
interactable.Interact();
// Immediately update the UI text so it switches from "Turn Off" to "Turn On" instantly
if (InteractionUI.Instance != null)
InteractionUI.Instance.UpdatePrompt(interactable.GetDescription());
}

View File

@@ -0,0 +1,119 @@
using UnityEngine;
using UnityEngine.SceneManagement;
public class StaircaseCurse : MonoBehaviour
{
[Header("Don't Look Back Settings")]
[Range(-1f, 1f)] public float killThreshold = -0.2f;
public GameObject blackScreenPanel;
[Header("Spooky Audio System")]
[Tooltip("Drag empty GameObjects with AudioSources from around the map here")]
public AudioSource[] environmentalSources;
public AudioClip[] scaryClips; // Whispers, footsteps, wood creaks
[Tooltip("Minimum seconds between random noises")]
public float minSoundDelay = 2f;
[Tooltip("Maximum seconds between random noises")]
public float maxSoundDelay = 5f;
// Internal State
private bool playerOnStairs = false;
private Transform playerTransform;
private bool isDead = false;
private float soundTimer;
void Start()
{
// Initialize timer
soundTimer = Random.Range(minSoundDelay, maxSoundDelay);
}
void Update()
{
if (isDead || !playerOnStairs) return;
// 1. CHECK LIGHTS & FACING DIRECTION
// If lights are OFF, the curse is active
if (GameManager.Instance != null && !GameManager.Instance.AreAnyLightsOn())
{
CheckPlayerFacing();
// 2. HANDLE RANDOM AUDIO
HandleAudio();
}
}
void HandleAudio()
{
soundTimer -= Time.deltaTime;
if (soundTimer <= 0)
{
PlayRandomSound();
// Reset timer
soundTimer = Random.Range(minSoundDelay, maxSoundDelay);
}
}
void PlayRandomSound()
{
if (environmentalSources.Length == 0 || scaryClips.Length == 0) return;
// 1. Pick a random sound
AudioClip clip = scaryClips[Random.Range(0, scaryClips.Length)];
// 2. Pick a random location (Source)
AudioSource source = environmentalSources[Random.Range(0, environmentalSources.Length)];
// 3. Play
source.PlayOneShot(clip);
Debug.Log($"Played sound from: {source.name}");
}
void CheckPlayerFacing()
{
// (Same logic as before)
Vector3 stairsDir = transform.forward;
Vector3 playerDir = playerTransform.forward;
stairsDir.y = 0; playerDir.y = 0;
stairsDir.Normalize(); playerDir.Normalize();
if (Vector3.Dot(stairsDir, playerDir) < killThreshold)
{
TriggerGameOver();
}
}
void TriggerGameOver()
{
isDead = true;
if (blackScreenPanel != null) blackScreenPanel.SetActive(true);
// Disable controls
if (playerTransform.GetComponent<PlayerController>())
playerTransform.GetComponent<PlayerController>().enabled = false;
Invoke("RestartLevel", 3f);
}
void RestartLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
playerOnStairs = true;
playerTransform = other.transform;
// Reset timer so a sound doesn't play INSTANTLY upon entering
soundTimer = Random.Range(minSoundDelay, maxSoundDelay);
}
}
void OnTriggerExit(Collider other)
{
if (other.CompareTag("Player")) playerOnStairs = false;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b7a7d453bbd891048ac8e2a2004e626a

View File

@@ -0,0 +1,45 @@
using UnityEngine;
public class StaircaseDistortion : MonoBehaviour
{
[Header("The Vertigo Effect")]
public float normalFOV = 60f;
public float stretchedFOV = 110f; // High FOV makes things look far away
public float transitionSpeed = 0.5f;
private Camera playerCam;
private bool isOnStairs = false;
void Update()
{
if (playerCam == null) return;
if (isOnStairs)
{
// Slowly widen the view (Stretch)
playerCam.fieldOfView = Mathf.Lerp(playerCam.fieldOfView, stretchedFOV, Time.deltaTime * transitionSpeed);
}
else
{
// Return to normal
playerCam.fieldOfView = Mathf.Lerp(playerCam.fieldOfView, normalFOV, Time.deltaTime * 2f);
}
}
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
playerCam = Camera.main; // Find the camera
isOnStairs = true;
}
}
void OnTriggerExit(Collider other)
{
if (other.CompareTag("Player"))
{
isOnStairs = false;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c220ca6b6a069cc49bb5e3067ac4350b

75
Assets/Scripts/WinZone.cs Normal file
View File

@@ -0,0 +1,75 @@
using UnityEngine;
using UnityEngine.UI; // Needed for UI
using System.Collections;
using TMPro; // Optional, if you want "Sweet Dreams" text
public class WinZone : MonoBehaviour
{
[Header("UI Settings")]
public GameObject blackScreenPanel;
public TextMeshProUGUI winText; // Assign a TMP text that says "Sweet Dreams"
public float fadeDuration = 3f;
[Header("Audio")]
public AudioSource playerAudioSource; // To stop footsteps/breathing
public AudioClip winSound; // Gentle lullaby or peaceful sound
private bool hasWon = false;
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player") && !hasWon)
{
// Only win if lights are OFF (optional check)
if (GameManager.Instance != null && !GameManager.Instance.AreAnyLightsOn())
{
StartCoroutine(WinSequence(other.gameObject));
}
}
}
IEnumerator WinSequence(GameObject player)
{
hasWon = true;
Debug.Log("Player reached the top! Winning...");
// 1. Disable Player Controls immediately
PlayerController pc = player.GetComponent<PlayerController>();
if (pc != null) pc.enabled = false;
// 2. Disable the Staircase Curse so they don't die while fading
StaircaseCurse curse = FindObjectOfType<StaircaseCurse>();
if (curse != null) curse.enabled = false;
// 3. Play Win Sound
if (playerAudioSource != null && winSound != null)
playerAudioSource.PlayOneShot(winSound);
// 4. Fade to Black
if (blackScreenPanel != null)
{
blackScreenPanel.SetActive(true);
CanvasGroup cg = blackScreenPanel.GetComponent<CanvasGroup>();
// If no CanvasGroup exists, add one
if (cg == null) cg = blackScreenPanel.AddComponent<CanvasGroup>();
float timer = 0f;
while (timer < fadeDuration)
{
timer += Time.deltaTime;
cg.alpha = timer / fadeDuration; // Fade from 0 to 1
yield return null;
}
cg.alpha = 1f;
}
// 5. Show Text
if (winText != null) winText.gameObject.SetActive(true);
// 6. Quit Game (or wait and load menu)
yield return new WaitForSeconds(3f);
Debug.Log("Quitting Application...");
Application.Quit();
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 66ef2fa00c381c2488388b8b3eaf6a46