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:
82
Assets/Scripts/DoorController.cs
Normal file
82
Assets/Scripts/DoorController.cs
Normal 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>";
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/DoorController.cs.meta
Normal file
2
Assets/Scripts/DoorController.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be04b116a17111d499850e75cbf8180c
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
45
Assets/Scripts/HauntedPhone.cs
Normal file
45
Assets/Scripts/HauntedPhone.cs
Normal 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...");
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/HauntedPhone.cs.meta
Normal file
2
Assets/Scripts/HauntedPhone.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fafa542c827db5641841e841b00e8388
|
||||
82
Assets/Scripts/HauntedPram.cs
Normal file
82
Assets/Scripts/HauntedPram.cs
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/HauntedPram.cs.meta
Normal file
2
Assets/Scripts/HauntedPram.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 677922238343afb44af86e0251cf2bfc
|
||||
50
Assets/Scripts/HauntedTV.cs
Normal file
50
Assets/Scripts/HauntedTV.cs
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/HauntedTV.cs.meta
Normal file
2
Assets/Scripts/HauntedTV.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69e32356c87a2b44f88628ea403dce6c
|
||||
44
Assets/Scripts/HeadBobber.cs
Normal file
44
Assets/Scripts/HeadBobber.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/HeadBobber.cs.meta
Normal file
2
Assets/Scripts/HeadBobber.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 23762b76f3f99214ebae97c1f93e2d92
|
||||
49
Assets/Scripts/InfiniteStairs.cs
Normal file
49
Assets/Scripts/InfiniteStairs.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/InfiniteStairs.cs.meta
Normal file
2
Assets/Scripts/InfiniteStairs.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35a98b200ae5fa14398a2ca7f1ff819e
|
||||
@@ -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>";
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
119
Assets/Scripts/StaircaseCurse.cs
Normal file
119
Assets/Scripts/StaircaseCurse.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/StaircaseCurse.cs.meta
Normal file
2
Assets/Scripts/StaircaseCurse.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b7a7d453bbd891048ac8e2a2004e626a
|
||||
45
Assets/Scripts/StaircaseDistortion.cs
Normal file
45
Assets/Scripts/StaircaseDistortion.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/StaircaseDistortion.cs.meta
Normal file
2
Assets/Scripts/StaircaseDistortion.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c220ca6b6a069cc49bb5e3067ac4350b
|
||||
75
Assets/Scripts/WinZone.cs
Normal file
75
Assets/Scripts/WinZone.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/WinZone.cs.meta
Normal file
2
Assets/Scripts/WinZone.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 66ef2fa00c381c2488388b8b3eaf6a46
|
||||
Reference in New Issue
Block a user