using System.Collections.Generic; using UnityEngine; /// /// Generates a simple endless platform path along the Z axis. /// Platforms are spawned with small X offsets, old chunks are removed behind the player. /// public class LevelGenerator : MonoBehaviour { public GameObject[] platformPrefabs; public Transform player; public int initialPlatformCount = 12; public float stepDistance = 3.5f; public float generationThreshold = 12f; public float removeThreshold = 18f; public float obstacleChance = 0.3f; public float sideOffsetRange = 2f; public bool infiniteMode = true; public int maxPlatformsAlive = 30; public LevelGenUI ui; public SimplePlayerController playerController; [SerializeField] private List spawnedPlatforms = new List(); private float lastGeneratedZ; private int generatedCount; private Vector3 initialPlayerPosition; private Quaternion initialPlayerRotation; private bool hasInitialPlayerPose; private void Awake() { CacheInitialPlayerPose(); } private void Start() { if (ui != null) { ui.SetGenerator(this); } RegenerateLevel(false); } private void Update() { if (Input.GetKeyDown(KeyCode.R)) { RegenerateLevel(); } if (player == null) { return; } RemoveOldPlatforms(); if (!infiniteMode) { return; } // Limit catch-up generation so a temporary teleport cannot spawn an uncontrolled amount in one frame. int generatedThisFrame = 0; while (player.position.z + generationThreshold > lastGeneratedZ && spawnedPlatforms.Count < maxPlatformsAlive && generatedThisFrame < 4) { GenerateNextPlatform(); generatedThisFrame++; } } public void GenerateInitialLevel() { ClearGeneratedLevel(); if (player == null || platformPrefabs == null || platformPrefabs.Length == 0) { Debug.LogWarning("LevelGenerator requires a player and at least one platform prefab."); return; } generatedCount = 0; Vector3 startPosition = player.position; lastGeneratedZ = startPosition.z - stepDistance; for (int i = 0; i < initialPlatformCount; i++) { GenerateNextPlatform(); } if (spawnedPlatforms.Count > 0) { PlacePlayerOnPlatform(spawnedPlatforms[0]); } } public GameObject GenerateNextPlatform() { if (platformPrefabs == null || platformPrefabs.Length == 0) { return null; } bool firstPlatform = generatedCount == 0; GameObject prefab = ChoosePlatformPrefab(firstPlatform); if (prefab == null) { return null; } float x = firstPlatform || player == null ? (player != null ? player.position.x : 0f) : Random.Range(-sideOffsetRange, sideOffsetRange); lastGeneratedZ += Mathf.Max(2.5f, stepDistance); Vector3 position = new Vector3(x, 0f, firstPlatform && player != null ? player.position.z : lastGeneratedZ); GameObject platform = Instantiate(prefab, position, Quaternion.identity, transform); platform.name = prefab.name + "_" + generatedCount.ToString("000"); PlatformInfo info = platform.GetComponent(); if (info != null) { info.generatedIndex = generatedCount; info.isSafe = firstPlatform || info.platformType != PlatformInfo.PlatformType.Obstacle; } spawnedPlatforms.Add(platform); generatedCount++; return platform; } public void RemoveOldPlatforms() { if (player == null) { return; } for (int i = spawnedPlatforms.Count - 1; i >= 0; i--) { GameObject platform = spawnedPlatforms[i]; if (platform == null) { spawnedPlatforms.RemoveAt(i); continue; } if (platform.transform.position.z < player.position.z - removeThreshold) { spawnedPlatforms.RemoveAt(i); DestroyPlatform(platform); } } } public void ClearGeneratedLevel() { HashSet scheduledForDestroy = new HashSet(); for (int i = spawnedPlatforms.Count - 1; i >= 0; i--) { if (spawnedPlatforms[i] != null) { scheduledForDestroy.Add(spawnedPlatforms[i]); DestroyPlatform(spawnedPlatforms[i]); } } spawnedPlatforms.Clear(); // Also remove generated children if the serialized list was lost after domain reload. for (int i = transform.childCount - 1; i >= 0; i--) { Transform child = transform.GetChild(i); if (child != null && !scheduledForDestroy.Contains(child.gameObject)) { DestroyPlatform(child.gameObject); } } } public void RegenerateLevel() { RegenerateLevel(true); } public void RegenerateLevel(bool showMessage) { CacheInitialPlayerPose(); ResetPlayer(); GenerateInitialLevel(); if (ui != null) { ui.UpdateInfo(); if (showMessage) { ui.ShowMessage("Уровень пересоздан"); } } } public int GetGeneratedCount() { return generatedCount; } public int GetAlivePlatformCount() { return spawnedPlatforms.Count; } private GameObject ChoosePlatformPrefab(bool firstPlatform) { if (firstPlatform) { return platformPrefabs[0]; } int obstacleIndex = FindObstaclePrefabIndex(); if (obstacleIndex >= 0 && Random.value < obstacleChance) { return platformPrefabs[obstacleIndex]; } List regularPrefabs = new List(); for (int i = 0; i < platformPrefabs.Length; i++) { if (i != obstacleIndex && platformPrefabs[i] != null) { regularPrefabs.Add(platformPrefabs[i]); } } if (regularPrefabs.Count == 0) { return platformPrefabs[0]; } return regularPrefabs[Random.Range(0, regularPrefabs.Count)]; } private int FindObstaclePrefabIndex() { for (int i = 0; i < platformPrefabs.Length; i++) { GameObject prefab = platformPrefabs[i]; if (prefab == null) { continue; } PlatformInfo info = prefab.GetComponent(); if (info != null && info.platformType == PlatformInfo.PlatformType.Obstacle) { return i; } } return -1; } private void CacheInitialPlayerPose() { if (hasInitialPlayerPose || player == null) { return; } initialPlayerPosition = player.position; initialPlayerRotation = player.rotation; hasInitialPlayerPose = true; } private void ResetPlayer() { if (player == null || !hasInitialPlayerPose) { return; } player.position = initialPlayerPosition; player.rotation = initialPlayerRotation; if (playerController == null) { playerController = player.GetComponent(); } if (playerController != null) { playerController.ResetToPosition(initialPlayerPosition); } } private void PlacePlayerOnPlatform(GameObject platform) { if (player == null || platform == null) { return; } if (!TryGetColliderBounds(platform, out Bounds bounds)) { return; } CapsuleCollider capsule = player.GetComponent(); float bottomOffset = 1f; if (capsule != null) { bottomOffset = (capsule.height * 0.5f * player.lossyScale.y) - (capsule.center.y * player.lossyScale.y); } Vector3 safePosition = new Vector3( bounds.center.x, bounds.max.y + bottomOffset + 0.05f, bounds.center.z); if (playerController == null) { playerController = player.GetComponent(); } if (playerController != null) { playerController.ResetToPosition(safePosition); } else { player.position = safePosition; } } private bool TryGetColliderBounds(GameObject platform, out Bounds bounds) { Collider[] colliders = platform.GetComponentsInChildren(); bounds = new Bounds(platform.transform.position, Vector3.zero); bool hasBounds = false; for (int i = 0; i < colliders.Length; i++) { Collider collider = colliders[i]; if (collider == null || collider.isTrigger) { continue; } if (!hasBounds) { bounds = collider.bounds; hasBounds = true; } else { bounds.Encapsulate(collider.bounds); } } return hasBounds; } private void DestroyPlatform(GameObject platform) { if (Application.isPlaying) { Destroy(platform); } else { DestroyImmediate(platform); } } }