Diego Ovalle 4 дней назад
Родитель
Сommit
8910f6f483

+ 1 - 0
.gitignore

@@ -98,3 +98,4 @@ InitTestScene*.unity*
 # Auto-generated scenes by play mode tests
 # Auto-generated scenes by play mode tests
 /[Aa]ssets/[Ii]nit[Tt]est[Ss]cene*.unity*
 /[Aa]ssets/[Ii]nit[Tt]est[Ss]cene*.unity*
 *.png
 *.png
+*.meta

BIN
Assets/Fantasy Skybox FREE/Scenes/Textures (Lightmaps)/LightingData.asset


+ 159 - 0
Assets/Prefabs/Enemy.prefab

@@ -0,0 +1,159 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &202005999698801233
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 2302696197064091558}
+  - component: {fileID: 6691465090084910139}
+  - component: {fileID: 5778799818540727974}
+  - component: {fileID: 8552493676810054830}
+  - component: {fileID: 5955966571847901740}
+  - component: {fileID: 7876811736464836383}
+  m_Layer: 0
+  m_Name: Enemy
+  m_TagString: Enemy
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &2302696197064091558
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 202005999698801233}
+  serializedVersion: 2
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 47.7, y: 38.6, z: 36.5}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_ConstrainProportionsScale: 0
+  m_Children: []
+  m_Father: {fileID: 0}
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!33 &6691465090084910139
+MeshFilter:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 202005999698801233}
+  m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0}
+--- !u!23 &5778799818540727974
+MeshRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 202005999698801233}
+  m_Enabled: 1
+  m_CastShadows: 1
+  m_ReceiveShadows: 1
+  m_DynamicOccludee: 1
+  m_StaticShadowCaster: 0
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
+  m_RayTracingMode: 2
+  m_RayTraceProcedural: 0
+  m_RayTracingAccelStructBuildFlagsOverride: 0
+  m_RayTracingAccelStructBuildFlags: 1
+  m_SmallMeshCulling: 1
+  m_ForceMeshLod: -1
+  m_MeshLodSelectionBias: 0
+  m_RenderingLayerMask: 1
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 2100000, guid: c75d5d5ea947e6c4daad6a480f6e99ba, type: 2}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 1
+  m_SelectedEditorRenderState: 3
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_GlobalIlluminationMeshLod: 0
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+  m_AdditionalVertexStreams: {fileID: 0}
+--- !u!114 &8552493676810054830
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 202005999698801233}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: be7e2f2d19bfdaa4f98e80193a0074ff, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: Assembly-CSharp::EnemyAI
+  speed: 5
+  rotationSpeed: 5
+  terrainLayer:
+    serializedVersion: 2
+    m_Bits: 64
+--- !u!135 &5955966571847901740
+SphereCollider:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 202005999698801233}
+  m_Material: {fileID: 0}
+  m_IncludeLayers:
+    serializedVersion: 2
+    m_Bits: 0
+  m_ExcludeLayers:
+    serializedVersion: 2
+    m_Bits: 0
+  m_LayerOverridePriority: 0
+  m_IsTrigger: 0
+  m_ProvidesContacts: 0
+  m_Enabled: 1
+  serializedVersion: 3
+  m_Radius: 0.5
+  m_Center: {x: 0, y: 0, z: 0}
+--- !u!54 &7876811736464836383
+Rigidbody:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 202005999698801233}
+  serializedVersion: 5
+  m_Mass: 1
+  m_LinearDamping: 0
+  m_AngularDamping: 0.05
+  m_CenterOfMass: {x: 0, y: 0, z: 0}
+  m_InertiaTensor: {x: 1, y: 1, z: 1}
+  m_InertiaRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_IncludeLayers:
+    serializedVersion: 2
+    m_Bits: 0
+  m_ExcludeLayers:
+    serializedVersion: 2
+    m_Bits: 0
+  m_ImplicitCom: 1
+  m_ImplicitTensor: 1
+  m_UseGravity: 0
+  m_IsKinematic: 0
+  m_Interpolate: 0
+  m_Constraints: 0
+  m_CollisionDetection: 0

+ 1 - 1
Assets/Prefabs/EnergyOrb.prefab

@@ -15,7 +15,7 @@ GameObject:
   - component: {fileID: 5064511320456291555}
   - component: {fileID: 5064511320456291555}
   m_Layer: 0
   m_Layer: 0
   m_Name: EnergyOrb
   m_Name: EnergyOrb
-  m_TagString: Untagged
+  m_TagString: Collectible
   m_Icon: {fileID: 0}
   m_Icon: {fileID: 0}
   m_NavMeshLayer: 0
   m_NavMeshLayer: 0
   m_StaticEditorFlags: 0
   m_StaticEditorFlags: 0

Разница между файлами не показана из-за своего большого размера
+ 14 - 64
Assets/Scenes/Valley.unity


+ 36 - 7
Assets/Scripts/DayNightCycle.cs

@@ -2,24 +2,53 @@ using UnityEngine;
 
 
 public class DayNightCycle : MonoBehaviour
 public class DayNightCycle : MonoBehaviour
 {
 {
+    [Header("Lights")]
+    public Transform sunLight;
+    public Transform moonLight;
+
+    [Header("Skybox Materials")]
+    public Material daySkybox;
+    public Material nightSkybox;
+
     [Header("Rotation Settings")]
     [Header("Rotation Settings")]
-    public float startRotationX = 10f; // Morning sun angle
-    public float endRotationX = 180f;  // Sunset/Night angle
+    public float startRotationX = 10f;
+    public float endRotationX = 350f;
+
+    [Range(0, 1)]
+    public float duskPercentage = 0.5f;
+    private bool hasTriggeredDusk = false;
 
 
     void Update()
     void Update()
     {
     {
         if (GameManager.Instance != null)
         if (GameManager.Instance != null)
         {
         {
-            // Calculate how much time has passed as a percentage (0 to 1)
             float totalDayTime = 300f;
             float totalDayTime = 300f;
             float timePassed = totalDayTime - GameManager.Instance.timeRemaining;
             float timePassed = totalDayTime - GameManager.Instance.timeRemaining;
             float timePercentage = timePassed / totalDayTime;
             float timePercentage = timePassed / totalDayTime;
 
 
-            // Interpolate the rotation from morning to night
-            float currentRotationX = Mathf.Lerp(startRotationX, endRotationX, timePercentage);
+            // 1. Handle Light Rotation
+            float currentSunX = Mathf.Lerp(startRotationX, endRotationX, timePercentage);
+            sunLight.rotation = Quaternion.Euler(currentSunX, -30f, 0f);
+            moonLight.rotation = Quaternion.Euler(currentSunX + 180f, -30f, 0f);
+
+            // 2. Handle Skybox Swap
+            // We swap to the night skybox a bit before the dusk enemies spawn
+            if (timePercentage > 0.45f)
+            {
+                RenderSettings.skybox = nightSkybox;
+            }
+            else
+            {
+                RenderSettings.skybox = daySkybox;
+            }
 
 
-            // Apply rotation to the light
-            transform.rotation = Quaternion.Euler(currentRotationX, -30f, 0f);
+            // 3. Dusk Enemies Trigger
+            if (timePercentage >= duskPercentage && !hasTriggeredDusk)
+            {
+                hasTriggeredDusk = true;
+                TerrainObjectSpawner spawner = FindObjectOfType<TerrainObjectSpawner>();
+                if (spawner != null) spawner.SpawnExtraEnemies(2);
+            }
         }
         }
     }
     }
 }
 }

+ 121 - 0
Assets/Scripts/EnemyAI.cs

@@ -0,0 +1,121 @@
+using UnityEngine;
+using System.Collections.Generic;
+
+public class EnemyAI : MonoBehaviour
+{
+    public enum EnemyState { Patrol, Chase, Returning }
+    public EnemyState currentState = EnemyState.Patrol;
+
+    [Header("Movement Settings")]
+    public float patrolSpeed = 3f;
+    public float chaseSpeed = 6f;
+    public float detectionRadius = 12f;
+    public float maxChaseDistance = 30f; // The "Leash" length
+
+    private Vector3 spawnPosition;
+    public List<Vector3> patrolPoints = new List<Vector3>();
+    private int currentPointIndex = 0;
+    private Transform player;
+    private LayerMask terrainLayer;
+
+    void Start()
+    {
+        player = GameObject.FindGameObjectWithTag("Player").transform;
+        // Looking for the "Terrain" layer specifically as per your Spawner
+        terrainLayer = LayerMask.GetMask("Terrain");
+        spawnPosition = transform.position; // Remember where it was born
+
+        // Generate 3 patrol points around spawn
+        for (int i = 0; i < 3; i++)
+        {
+            Vector3 randomPos = spawnPosition + new Vector3(Random.Range(-15f, 15f), 10f, Random.Range(-15f, 15f));
+            if (Physics.Raycast(randomPos, Vector3.down, out RaycastHit hit, 20f, terrainLayer))
+            {
+                patrolPoints.Add(hit.point + Vector3.up * 1f);
+            }
+        }
+    }
+
+    void Update()
+    {
+        float distToPlayer = Vector3.Distance(transform.position, player.position);
+        float distFromHome = Vector3.Distance(transform.position, spawnPosition);
+
+        // State Logic
+        if (currentState == EnemyState.Chase && distFromHome > maxChaseDistance)
+        {
+            currentState = EnemyState.Returning;
+        }
+        else if (distToPlayer < detectionRadius && currentState != EnemyState.Returning)
+        {
+            currentState = EnemyState.Chase;
+        }
+        else if (currentState == EnemyState.Returning && distFromHome < 2f)
+        {
+            currentState = EnemyState.Patrol;
+        }
+
+        ExecuteState();
+    }
+
+    void ExecuteState()
+    {
+        switch (currentState)
+        {
+            case EnemyState.Patrol:
+                Patrol();
+                break;
+            case EnemyState.Chase:
+                MoveTowards(player.position, chaseSpeed);
+                break;
+            case EnemyState.Returning:
+                MoveTowards(spawnPosition, patrolSpeed);
+                break;
+        }
+    }
+
+    void Patrol()
+    {
+        if (patrolPoints.Count == 0) return;
+        Vector3 target = patrolPoints[currentPointIndex];
+        MoveTowards(target, patrolSpeed);
+
+        if (Vector3.Distance(transform.position, target) < 1.5f)
+            currentPointIndex = (currentPointIndex + 1) % patrolPoints.Count;
+    }
+
+    void MoveTowards(Vector3 target, float speed)
+    {
+        Vector3 dir = (target - transform.position).normalized;
+        dir.y = 0;
+
+        // 1. Check for obstacles directly in front
+        // We fire a ray from the "chest" of the enemy forward
+        RaycastHit wallHit;
+        if (Physics.Raycast(transform.position + Vector3.up * 1f, dir, out wallHit, 1.5f, terrainLayer))
+        {
+            // If the hill is too steep, we try to "step up" or slide along it
+            dir += wallHit.normal * 0.5f;
+        }
+
+        // 2. Apply movement
+        transform.position += dir * speed * Time.deltaTime;
+
+        // 3. Ground Hugging (Smooth height adjustment)
+        // We use a longer ray and a SmoothDamp or Lerp to prevent snapping
+        if (Physics.Raycast(transform.position + Vector3.up * 10f, Vector3.down, out RaycastHit groundHit, 20f, terrainLayer))
+        {
+            float targetHeight = groundHit.point.y + 0.5f;
+            // Smoothly transition the Y position so they don't "teleport" up hills
+            float newY = Mathf.MoveTowards(transform.position.y, targetHeight, speed * Time.deltaTime);
+            transform.position = new Vector3(transform.position.x, newY, transform.position.z);
+        }
+
+        // 4. Rotation (Make them face where they are going)
+        if (dir != Vector3.zero)
+        {
+            Quaternion targetRotation = Quaternion.LookRotation(dir);
+            transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * 5f);
+        }
+    }
+}

+ 31 - 0
Assets/Scripts/GameData.cs

@@ -0,0 +1,31 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+[System.Serializable]
+public class SaveVector3
+{
+    public float x, y, z;
+    public SaveVector3(Vector3 position)
+    {
+        x = position.x;
+        y = position.y;
+        z = position.z;
+    }
+    public Vector3 ToVector3() => new Vector3(x, y, z);
+}
+
+[System.Serializable]
+public class GameData
+{
+    public int mapSeed;
+    public float remainingTimer;
+    public int orbsCollected;
+    public float[] playerPos;
+    public float[] playerRot;
+
+    // Use the wrapper instead of float[]
+    public List<SaveVector3> orbPositions = new List<SaveVector3>();
+    public List<SaveVector3> enemyPositions = new List<SaveVector3>();
+    public List<int> enemyStates = new List<int>();
+    public float masterVolume;
+}

+ 76 - 4
Assets/Scripts/GameManager.cs

@@ -14,9 +14,23 @@ public class GameManager : MonoBehaviour
     public TextMeshProUGUI orbText;
     public TextMeshProUGUI orbText;
     public TextMeshProUGUI timerText;
     public TextMeshProUGUI timerText;
 
 
+    [Header("Game Over UI")]
+    public GameObject gameOverPanel;
+    public TextMeshProUGUI gameOverTitleText;
+
     private int currentOrbs = 0;
     private int currentOrbs = 0;
     private bool isGameOver = false;
     private bool isGameOver = false;
 
 
+    public int GetOrbsInternal() { return currentOrbs; }
+
+    public void SetDataInternal(int orbs, float time)
+    {
+        currentOrbs = orbs;
+        timeRemaining = time;
+        UpdateOrbUI();
+        UpdateTimerUI();
+    }
+
     void Awake()
     void Awake()
     {
     {
         // Singleton pattern: ensures only one GameManager exists
         // Singleton pattern: ensures only one GameManager exists
@@ -51,6 +65,37 @@ public class GameManager : MonoBehaviour
         }
         }
     }
     }
 
 
+    public void SaveProgress()
+    {
+        PlayerPrefs.SetInt("SavedOrbs", currentOrbs);
+        PlayerPrefs.SetFloat("SavedTime", timeRemaining);
+        PlayerPrefs.Save();
+        Debug.Log("Game Saved! Orbs: " + currentOrbs);
+    }
+
+    public void LoadProgress()
+    {
+        if (PlayerPrefs.HasKey("SavedOrbs"))
+        {
+            currentOrbs = PlayerPrefs.GetInt("SavedOrbs");
+            timeRemaining = PlayerPrefs.GetFloat("SavedTime");
+
+            UpdateOrbUI();
+            UpdateTimerUI();
+            Debug.Log("Game Loaded!");
+        }
+        else
+        {
+            Debug.Log("No Save Data Found.");
+        }
+    }
+
+    public void ClearSave()
+    {
+        PlayerPrefs.DeleteAll();
+        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
+    }
+
     void UpdateOrbUI()
     void UpdateOrbUI()
     {
     {
         orbText.text = "Orbs: " + currentOrbs + " / " + totalOrbsToWin;
         orbText.text = "Orbs: " + currentOrbs + " / " + totalOrbsToWin;
@@ -63,12 +108,39 @@ public class GameManager : MonoBehaviour
         timerText.text = string.Format("Time: {0:00}:{1:00}", minutes, seconds);
         timerText.text = string.Format("Time: {0:00}:{1:00}", minutes, seconds);
     }
     }
 
 
-    void GameOver(bool won)
+    public void GameOver(bool won)
     {
     {
         isGameOver = true;
         isGameOver = true;
-        if (won) Debug.Log("VICTORY! All orbs collected.");
-        else Debug.Log("GAME OVER! Night has fallen.");
+        Time.timeScale = 0f; // Freeze the game
+        gameOverPanel.SetActive(true);
 
 
-        // Later we will trigger Victory/Loss screens here
+        if (won)
+        {
+            gameOverTitleText.text = "VICTORY!";
+            gameOverTitleText.color = Color.green;
+            Debug.Log("Winner!");
+        }
+        else
+        {
+            gameOverTitleText.text = "NIGHT HAS FALLEN...";
+            gameOverTitleText.color = Color.red;
+            Debug.Log("Game Over!");
+        }
+
+        // Unlock cursor so player can click buttons
+        Cursor.lockState = CursorLockMode.None;
+        Cursor.visible = true;
+    }
+
+    public void RestartGame()
+    {
+        Time.timeScale = 1f;
+        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
+    }
+
+    public void LoadMainMenu()
+    {
+        Time.timeScale = 1f;
+        SceneManager.LoadScene("MainMenu"); // Make sure your menu scene is named this
     }
     }
 }
 }

+ 61 - 0
Assets/Scripts/PauseMenu.cs

@@ -0,0 +1,61 @@
+using UnityEngine;
+using UnityEngine.InputSystem;
+
+public class PauseMenu : MonoBehaviour
+{
+    public static bool GameIsPaused = false;
+    public GameObject pauseMenuUI;
+    private SaveSystem saveSystem;
+
+    void Start()
+    {
+        saveSystem = FindFirstObjectByType<SaveSystem>();
+    }
+
+    void Update()
+    {
+        // F Key to Toggle Pause
+        if (Keyboard.current.fKey.wasPressedThisFrame)
+        {
+            if (GameIsPaused) Resume();
+            else Pause();
+        }
+
+        // F5 to Save 
+        if (Keyboard.current.f5Key.wasPressedThisFrame)
+        {
+            saveSystem.SaveGame();
+        }
+
+        // F9 to Load 
+        if (Keyboard.current.f9Key.wasPressedThisFrame)
+        {
+            saveSystem.LoadGame();
+        }
+    }
+
+    public void Resume()
+    {
+        pauseMenuUI.SetActive(false);
+        Time.timeScale = 1f;
+        GameIsPaused = false;
+        Cursor.lockState = CursorLockMode.Locked;
+        Cursor.visible = false;
+    }
+
+    void Pause()
+    {
+        pauseMenuUI.SetActive(true);
+        Time.timeScale = 0f;
+        GameIsPaused = true;
+        Cursor.lockState = CursorLockMode.None;
+        Cursor.visible = true;
+    }
+
+    public void QuitToMenu()
+    {
+        Time.timeScale = 1f;
+        // Adjust "MainMenu" to your actual Main Menu scene name later
+        UnityEngine.SceneManagement.SceneManager.LoadScene("MainMenu");
+    }
+}

+ 50 - 15
Assets/Scripts/PlayerStats.cs

@@ -1,48 +1,76 @@
 using UnityEngine;
 using UnityEngine;
 using UnityEngine.UI;
 using UnityEngine.UI;
+using StarterAssets; // Required to access the ThirdPersonController
 
 
 public class PlayerStats : MonoBehaviour
 public class PlayerStats : MonoBehaviour
 {
 {
+    [Header("Stamina Settings")]
     public float maxStamina = 100f;
     public float maxStamina = 100f;
     public float currentStamina;
     public float currentStamina;
-    public float drainRate = 25f; // Increased for testing
+    public float drainRate = 25f;
     public float regenRate = 15f;
     public float regenRate = 15f;
     public Slider staminaSlider;
     public Slider staminaSlider;
 
 
-    private CharacterController _characterController;
+    [Header("Movement Settings (Overrides)")]
+    public float walkSpeed = 6f;    // Set to 6 to be faster than enemies
+    public float sprintSpeed = 12f; // Fast escape speed
+    public float jumpHeight = 3f;   // Better navigation for scale 1.0
+
+    private ThirdPersonController _controller;
+    private StarterAssetsInputs _inputs;
 
 
     void Start()
     void Start()
     {
     {
         currentStamina = maxStamina;
         currentStamina = maxStamina;
-        _characterController = GetComponent<CharacterController>();
 
 
-        // Set the slider's max value to match our code automatically
+        // Find the components on the PlayerArmature
+        _controller = GetComponent<ThirdPersonController>();
+        _inputs = GetComponent<StarterAssetsInputs>();
+
+        // Set the UI and initial controller values
         if (staminaSlider != null)
         if (staminaSlider != null)
         {
         {
             staminaSlider.maxValue = maxStamina;
             staminaSlider.maxValue = maxStamina;
             staminaSlider.value = currentStamina;
             staminaSlider.value = currentStamina;
         }
         }
+
+        if (_controller != null)
+        {
+            _controller.MoveSpeed = walkSpeed;
+            _controller.SprintSpeed = sprintSpeed;
+            _controller.JumpHeight = jumpHeight;
+        }
     }
     }
 
 
     void Update()
     void Update()
     {
     {
-        // 1. Get the actual horizontal movement speed
-        Vector3 horizontalVelocity = new Vector3(_characterController.velocity.x, 0, _characterController.velocity.z);
-        float currentSpeed = horizontalVelocity.magnitude;
-
-        // 2. Check if we are moving faster than a basic walk
-        // In Starter Assets, Walk is usually 2.0 and Run is 6.0. 
-        // We check for > 3.0 to be safe.
-        if (currentSpeed > 3.0f)
+        // 1. Logic: Are we moving AND holding the sprint key?
+        bool isMoving = _inputs.move != Vector2.zero;
+        bool isSprinting = _inputs.sprint && isMoving;
+
+        // 2. Handle Stamina Drain/Regen
+        if (isSprinting && currentStamina > 0)
         {
         {
             currentStamina -= drainRate * Time.deltaTime;
             currentStamina -= drainRate * Time.deltaTime;
+
+            // If we run out of breath
+            if (currentStamina <= 0)
+            {
+                currentStamina = 0;
+                ForceWalk(); // Force the controller to slow down
+            }
         }
         }
         else
         else
         {
         {
-            currentStamina += regenRate * Time.deltaTime;
-        }
+            if (currentStamina < maxStamina)
+                currentStamina += regenRate * Time.deltaTime;
 
 
-        Debug.Log("Current Speed: " + currentSpeed);
+            // If not actively sprinting, ensure speed is walk speed
+            if (!isSprinting)
+            {
+                _controller.MoveSpeed = walkSpeed;
+            }
+        }
 
 
         // 3. Keep stamina between 0 and 100
         // 3. Keep stamina between 0 and 100
         currentStamina = Mathf.Clamp(currentStamina, 0, maxStamina);
         currentStamina = Mathf.Clamp(currentStamina, 0, maxStamina);
@@ -53,4 +81,11 @@ public class PlayerStats : MonoBehaviour
             staminaSlider.value = currentStamina;
             staminaSlider.value = currentStamina;
         }
         }
     }
     }
+
+    // Helper to stop the sprint even if the player is holding Shift
+    private void ForceWalk()
+    {
+        _controller.MoveSpeed = walkSpeed;
+        _inputs.sprint = false; // This "un-clicks" the sprint for the player
+    }
 }
 }

+ 113 - 0
Assets/Scripts/SaveSystem.cs

@@ -0,0 +1,113 @@
+using System.IO;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class SaveSystem : MonoBehaviour
+{
+    private string savePath;
+
+    [Header("References")]
+    public MapGenerator mapGenerator;
+    public GameObject enemyPrefab;
+    public GameObject orbPrefab;
+    public Transform playerTransform;
+
+    void Awake()
+    {
+        savePath = Path.Combine(Application.persistentDataPath, "savegame.json");
+    }
+
+    public void SaveGame()
+    {
+        GameData data = new GameData();
+
+        // 1. Stats from GameManager
+        data.orbsCollected = GameManager.Instance.GetOrbsInternal();
+        data.remainingTimer = GameManager.Instance.timeRemaining;
+        data.mapSeed = mapGenerator.noiseData.seed;
+
+        // 2. Player Transform
+        data.playerPos = new float[] { playerTransform.position.x, playerTransform.position.y, playerTransform.position.z };
+        data.playerRot = new float[] { playerTransform.eulerAngles.x, playerTransform.eulerAngles.y, playerTransform.eulerAngles.z };
+
+        // 3. Collectibles - Ensure lists are initialized 
+        data.orbPositions = new List<SaveVector3>();
+        GameObject[] orbs = GameObject.FindGameObjectsWithTag("Collectible");
+        Debug.Log("SaveSystem: Found " + orbs.Length + " orbs to save.");
+
+        foreach (GameObject orb in orbs)
+        {
+            data.orbPositions.Add(new SaveVector3(orb.transform.position));
+        }
+
+        // 4. Enemies - Ensure lists are initialized 
+        data.enemyPositions = new List<SaveVector3>();
+        data.enemyStates = new List<int>();
+        GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy");
+        Debug.Log("SaveSystem: Found " + enemies.Length + " enemies to save.");
+
+        foreach (GameObject enemy in enemies)
+        {
+            EnemyAI ai = enemy.GetComponent<EnemyAI>();
+            if (ai != null)
+            {
+                data.enemyPositions.Add(new SaveVector3(enemy.transform.position));
+                data.enemyStates.Add((int)ai.currentState);
+            }
+        }
+
+        // Convert to JSON and save
+        string json = JsonUtility.ToJson(data, true);
+        File.WriteAllText(savePath, json);
+        Debug.Log("Save file written to: " + savePath);
+    }
+
+    public void LoadGame()
+    {
+        if (!File.Exists(savePath))
+        {
+            Debug.LogWarning("No Save File Found at " + savePath);
+            return;
+        }
+
+        string json = File.ReadAllText(savePath);
+        GameData data = JsonUtility.FromJson<GameData>(json);
+
+        // 1. Restore Map Seed & Regenerate 
+        mapGenerator.noiseData.seed = data.mapSeed;
+        mapGenerator.DrawMapInEditor();
+
+        // Refresh Collider for the new terrain shape
+        MeshFilter mf = mapGenerator.GetComponent<MeshFilter>();
+        mapGenerator.GetComponent<MeshCollider>().sharedMesh = mf.sharedMesh;
+
+        // 2. Restore Stats in GameManager
+        GameManager.Instance.SetDataInternal(data.orbsCollected, data.remainingTimer);
+
+        // 3. Restore Player Position
+        CharacterController cc = playerTransform.GetComponent<CharacterController>();
+        cc.enabled = false; // Disable to allow direct position setting
+        playerTransform.position = new Vector3(data.playerPos[0], data.playerPos[1], data.playerPos[2]);
+        playerTransform.rotation = Quaternion.Euler(data.playerRot[0], data.playerRot[1], data.playerRot[2]);
+        cc.enabled = true;
+
+        // 4. Restore Orbs
+        Debug.Log("Attempting to load " + data.orbPositions.Count + " orbs.");
+        foreach (GameObject o in GameObject.FindGameObjectsWithTag("Collectible")) Destroy(o);
+        foreach (SaveVector3 sPos in data.orbPositions)
+        {
+            Instantiate(orbPrefab, sPos.ToVector3(), Quaternion.identity);
+        }
+
+        // 5. Restore Enemies
+        Debug.Log("Attempting to load " + data.enemyPositions.Count + " enemies.");
+        foreach (GameObject e in GameObject.FindGameObjectsWithTag("Enemy")) Destroy(e);
+        for (int i = 0; i < data.enemyPositions.Count; i++)
+        {
+            GameObject newEnemy = Instantiate(enemyPrefab, data.enemyPositions[i].ToVector3(), Quaternion.identity);
+            newEnemy.GetComponent<EnemyAI>().currentState = (EnemyAI.EnemyState)data.enemyStates[i];
+        }
+
+        Debug.Log("Detailed JSON Load Complete!");
+    }
+}

+ 2 - 2
Assets/Scripts/TerrainGen/Data/NoiseData.asset

@@ -14,9 +14,9 @@ MonoBehaviour:
   m_EditorClassIdentifier: Assembly-CSharp::NoiseData
   m_EditorClassIdentifier: Assembly-CSharp::NoiseData
   autoUpdate: 1
   autoUpdate: 1
   normalizeMode: 0
   normalizeMode: 0
-  noiseScale: 50
+  noiseScale: 55
   octaves: 6
   octaves: 6
   persistance: 0.471
   persistance: 0.471
   lacunarity: 2
   lacunarity: 2
-  seed: 47100
+  seed: 75634
   offset: {x: 0, y: 0}
   offset: {x: 0, y: 0}

+ 1 - 1
Assets/Scripts/TerrainGen/Data/TerrainData.asset

@@ -13,7 +13,7 @@ MonoBehaviour:
   m_Name: TerrainData
   m_Name: TerrainData
   m_EditorClassIdentifier: Assembly-CSharp::TerrainData
   m_EditorClassIdentifier: Assembly-CSharp::TerrainData
   autoUpdate: 1
   autoUpdate: 1
-  uniformScale: 2.5
+  uniformScale: 1
   useFlatShading: 0
   useFlatShading: 0
   useFalloff: 0
   useFalloff: 0
   meshHeightMultiplier: 25
   meshHeightMultiplier: 25

+ 1 - 0
Assets/Scripts/TerrainGen/MapGenerator.cs

@@ -145,6 +145,7 @@ public class MapGenerator : MonoBehaviour {
         if (spawner != null)
         if (spawner != null)
         {
         {
             spawner.SpawnOrbs();
             spawner.SpawnOrbs();
+			spawner.SpawnEnemies();
         }
         }
         else
         else
         {
         {

+ 64 - 18
Assets/Scripts/TerrainObjectSpawner.cs

@@ -1,4 +1,5 @@
 using UnityEngine;
 using UnityEngine;
+using System.Collections.Generic; // Added for List
 
 
 public class TerrainObjectSpawner : MonoBehaviour
 public class TerrainObjectSpawner : MonoBehaviour
 {
 {
@@ -6,45 +7,90 @@ public class TerrainObjectSpawner : MonoBehaviour
     public int orbCount = 20;
     public int orbCount = 20;
     public LayerMask terrainLayer;
     public LayerMask terrainLayer;
     public float meshMaxHeight = 25f;
     public float meshMaxHeight = 25f;
+    public GameObject enemyPrefab;
+    public int enemyCount = 5; 
+    public float minDistanceBetweenOrbs = 15f; 
 
 
     public void SpawnOrbs()
     public void SpawnOrbs()
     {
     {
         Debug.Log("Spawner: Starting process...");
         Debug.Log("Spawner: Starting process...");
-
-        if (orbPrefab == null)
-        {
-            Debug.LogError("Spawner: ORB PREFAB IS MISSING IN INSPECTOR!");
-            return;
-        }
+        if (orbPrefab == null) return;
 
 
         int spawned = 0;
         int spawned = 0;
         int attempts = 0;
         int attempts = 0;
+        List<Vector3> spawnedPositions = new List<Vector3>();
 
 
-        while (spawned < orbCount && attempts < 500)
+        while (spawned < orbCount && attempts < 1000) // Increased attempts for better scattering
         {
         {
             attempts++;
             attempts++;
-            // Lague's map is centered, so we use a wide range
             float x = Random.Range(-100f, 100f);
             float x = Random.Range(-100f, 100f);
             float z = Random.Range(-100f, 100f);
             float z = Random.Range(-100f, 100f);
+            Vector3 rayStart = new Vector3(x, 500f, z);
 
 
-            Vector3 rayStart = new Vector3(x, 500f, z); // Start very high up
-
-            // TEST RAYCAST
             if (Physics.Raycast(rayStart, Vector3.down, out RaycastHit hit, 1000f, terrainLayer))
             if (Physics.Raycast(rayStart, Vector3.down, out RaycastHit hit, 1000f, terrainLayer))
             {
             {
-                // If it hits, spawn the orb immediately for now (ignoring height logic)
-                Instantiate(orbPrefab, hit.point + Vector3.up * 1.5f, Quaternion.identity);
-                spawned++;
+                // Check distance from previously spawned orbs
+                bool isTooClose = false;
+                foreach (Vector3 pos in spawnedPositions)
+                {
+                    if (Vector3.Distance(hit.point, pos) < minDistanceBetweenOrbs)
+                    {
+                        isTooClose = true;
+                        break;
+                    }
+                }
+
+                if (!isTooClose)
+                {
+                    Instantiate(orbPrefab, hit.point + Vector3.up * 1.5f, Quaternion.identity);
+                    spawnedPositions.Add(hit.point);
+                    spawned++;
+                }
             }
             }
         }
         }
+        Debug.Log("Spawner: Successfully spawned " + spawned + " scattered orbs!");
+    }
 
 
-        if (spawned == 0)
+    public void SpawnEnemies()
+    {
+        int spawned = 0;
+        int attempts = 0;
+        while (spawned < enemyCount && attempts < 500)
         {
         {
-            Debug.LogWarning("Spawner: 0 Orbs spawned. Raycasts hit NOTHING. Check your Layer settings!");
+            attempts++;
+            float x = Random.Range(-100f, 100f);
+            float z = Random.Range(-100f, 100f);
+            Vector3 rayStart = new Vector3(x, 500f, z);
+
+            if (Physics.Raycast(rayStart, Vector3.down, out RaycastHit hit, 1000f, terrainLayer))
+            {
+                if (hit.point.y > meshMaxHeight * 0.5f)
+                {
+                    Instantiate(enemyPrefab, hit.point + Vector3.up * 2f, Quaternion.identity);
+                    spawned++;
+                }
+            }
         }
         }
-        else
+    }
+
+    public void SpawnExtraEnemies(int amount)
+    {
+        int extraSpawned = 0;
+        while (extraSpawned < amount)
         {
         {
-            Debug.Log("Spawner: Successfully spawned " + spawned + " orbs!");
+            float x = Random.Range(-100f, 100f);
+            float z = Random.Range(-100f, 100f);
+            Vector3 rayStart = new Vector3(x, 500f, z);
+
+            if (Physics.Raycast(rayStart, Vector3.down, out RaycastHit hit, 1000f, terrainLayer))
+            {
+                // Spawn them on hills as usual
+                if (hit.point.y > meshMaxHeight * 0.4f)
+                {
+                    Instantiate(enemyPrefab, hit.point + Vector3.up * 2f, Quaternion.identity);
+                    extraSpawned++;
+                }
+            }
         }
         }
     }
     }
 }
 }

+ 0 - 8
Assets/Textures.meta

@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: 81dcbb45a1cbe094fbfcee4c5f3ac5b7
-folderAsset: yes
-DefaultImporter:
-  externalObjects: {}
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

+ 2 - 0
ProjectSettings/TagManager.asset

@@ -5,6 +5,8 @@ TagManager:
   serializedVersion: 3
   serializedVersion: 3
   tags:
   tags:
   - CinemachineTarget
   - CinemachineTarget
+  - Enemy
+  - Collectible
   layers:
   layers:
   - Default
   - Default
   - TransparentFX
   - TransparentFX

Некоторые файлы не были показаны из-за большого количества измененных файлов