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);
}
}
}