using System;
using System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector;
using UnityEngine;
using Random = UnityEngine.Random;
public class TerrainGenerator : MonoBehaviour
{
[Header("Common Settings")]
[SerializeField] [Range(1, 35)] int width = 12;
[SerializeField] [Range(1, 35)] int height = 8;
[SerializeField] [ReadOnly] int _lastSeed;
[SerializeField] int _seed;
[Header("Perlin Settings")]
[SerializeField] bool _usePerlin;
[SerializeField] int horizontalScroll;
[SerializeField] int verticalScroll;
[SerializeField] [Range(1, 20)] float scale = 20f;
[Header("Tiles")]
[SerializeField] float[] _tileHeights;
[SerializeField] float _borderHeight = 1f;
[SerializeField] List<GameObject> terrainTilePrefabs;
[SerializeField] GameObject terrainTileBorderPrefab;
[ReadOnly] public List<GameObject> terrainTiles = new();
int _lastSizeX;
int _lastSizeZ;
void OnValidate()
{
if (_tileHeights.Length != terrainTilePrefabs.Count)
Array.Resize(ref _tileHeights, terrainTilePrefabs.Count);
if (_seed != _lastSeed || width != _lastSizeX || height != _lastSizeZ)
{
_lastSeed = _seed;
GenerateTerrain();
}
}
void DestroyExistingTerrain()
{
if (terrainTiles == null)
return;
terrainTiles = GetComponentsInChildren<MeshRenderer>().Select(t => t.gameObject).ToList();
foreach (var tile in terrainTiles)
{
if (Application.isPlaying)
Destroy(tile.gameObject);
else
DestroyImmediate(tile.gameObject);
}
terrainTiles.Clear();
}
public static float Map(float value, float originalMin, float originalMax, float targetMin, float targetMax)
{
return (value - originalMin) / (originalMax - originalMin) * (targetMax - targetMin) + targetMin;
}
[ContextMenu("Generate Terrain")]
public void GenerateTerrain()
{
DestroyExistingTerrain();
Random.InitState(_seed);
var randomOffsetX = Random.value * 100f;
var randomOffsetZ = Random.value * 100f;
for (var x = -width; x < width; x++)
for (var z = -height; z < height; z++)
{
int terrainIndex = Random.Range(0, terrainTilePrefabs.Count);
if (_usePerlin)
{
var xCoord = (x + randomOffsetX + horizontalScroll) / width * scale;
var zCoord = (z + randomOffsetZ + verticalScroll) / height * scale;
var y = Mathf.PerlinNoise(xCoord, zCoord);
terrainIndex = (int) Map(y, 0, 1, 0, terrainTilePrefabs.Count);
terrainIndex = Mathf.Clamp(terrainIndex, 0, terrainTilePrefabs.Count - 1);
}
GameObject terrainTile = terrainTilePrefabs[terrainIndex];
var isBorder = x == -width || x == width - 1 || z == -height || z == height - 1;
if (isBorder)
terrainTile = terrainTileBorderPrefab;
var tile = Instantiate(terrainTile, new Vector3(x, 0, z), Quaternion.identity, transform);
terrainTiles.Add(tile);
if (isBorder)
tile.transform.localPosition += Vector3.up * _borderHeight;
else
tile.transform.localPosition += Vector3.up * _tileHeights[terrainIndex];
}
}
}