Tu assignes ce script à un terrain, tu mets le cube par défaut d'unity dans la variable Mesh (ça ne créé pas des gameobject, mais affiche les cubes en wireframe avec des gizmos), et tu n'auras plus qu'à jouer avec les réglages.
Pour relancer le calcul après un changement de réglages, il faut désactiver puis réactiver le script dans l'inspecteur.
C'est pas ultra optimal au niveau des performances (même si j'ai bidouillé un poil pour que ça reste raisonnable), une autre approche éventuellement plus rapide serait de lancer aléatoirement des rayons sur toute la surface du terrain, et dès qu'un spot correct est détecté, y placer un cube, puis à partir de cette position calculer la position des cubes voisins, de proche en proche tant que le sol est plat.
J'ai commenté l'essentiel ... mais si tu as des questions, j'y répondrai avec plaisir
Code : Tout sélectionner
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
[ExecuteInEditMode]
public class RandomCubeSpawner : MonoBehaviour {
struct CubeData{
public Vector3 position;
public float size;
public float angle;
public float diagonale;
public CubeData(Vector3 position, float size, float angle, float diagonale){
this.position = position;
this.size = size;
this.angle = angle;
this.diagonale = diagonale;
}
}
private List<CubeData> cubesData = new List<CubeData>();
[Range(0f,1f)]
public float coverage = 1f;
public Mesh cubeMesh;
public bool multiPass = false;
[Range(1f,20f)]
public float margin = 1f;
[Range(1f,200f)]
public float minHeight = 1f;
[Range(1f,20f)]
public float minSize = 1f;
[Range(1f,20f)]
public float maxSize = 5f;
void OnEnable(){
PlaceCubes ();
}
void PlaceCubes(){
double now = Time.realtimeSinceStartup;
Terrain terrain = this.GetComponent<Terrain> ();
Vector3 mapSize = terrain.terrainData.size;
// purge
cubesData.Clear ();
// initialisation de la reandom seed pour toujours obtenir le même résultat
Random.seed = 123;
// nombre de cubes créés
int total = 0;
int processed = 0;
RaycastHit hit,hitCorner;
if (minSize > maxSize) {
float m = maxSize;
maxSize = minSize;
minSize = m;
}
// le placement des cubes est fait en plusieurs passes pour permettre de placer d'abord les gros, puis les petits dans les espaces restants
int stepCount = Mathf.CeilToInt((maxSize - minSize) / 5);
// dans le cas d'une création en plusieurs passes, on commence par créer les plus gros cubes
// puis on réduit la valeur max à chaque passe consécutive
float endSize = maxSize;
float sizeStep = (maxSize - minSize) / (stepCount + 1);
if (sizeStep < minSize || !multiPass) {
stepCount = 1;
}
for (int s = 0; s < stepCount; s++) {
Vector3 randomPos = Vector3.zero;
Quaternion quat = Quaternion.identity;
Vector3 pos = Vector3.zero;
float size = 1f;
float diagonale = 1f;
float moveStep = minSize * 0.5f;
Ray r = new Ray (randomPos, Vector3.down);
Ray test = new Ray (randomPos, Vector3.down);
bool createCube = true;
float randomAngle;
while (pos.x < mapSize.x) {
pos.z = 0f;
while (pos.z < mapSize.z) {
processed++;
size = Random.Range (minSize, endSize);
diagonale = Mathf.Sqrt (size * size * 2);
randomPos = pos + Random.insideUnitSphere * size;
randomPos.y = 500;
r.origin = randomPos;
createCube = Physics.Raycast (r, out hit);
if (createCube) {
// si le terrain est plat à cet endroit
if (hit.normal == Vector3.up && hit.point.y >= minHeight) {
// recherche d'un cube proche dans les positions précédentes
// on commence par la fin de la liste car il y a plus de chances de trouver un cube proche dans les derniers ajoutés
for (int j = cubesData.Count - 1; j >= 0; j--) {
// le test est fait sur la distance entre les deux centres des cubes, et selon leurs diagonales (ils sont donc considérés comme des sphères)
if (Vector3.Distance (cubesData [j].position, hit.point) <= (diagonale + cubesData[j].diagonale) * 0.5f) {
createCube = false;
break;
}
}
// si aucun autre cube ne gène
if (createCube) {
// angle de rotation aléatoire du cube
randomAngle = Random.Range (0f, 360);
float offset = (size + margin * 2) * 0.5f;
// ray cast sur les 4 coins et bords pour détecter s'ils sont dans le vide
for (var k = 0; k < 360; k += 90) {
quat.eulerAngles = new Vector3 (0, randomAngle + k, 0);
// positionnement du rayon sur un coin du cube
r.origin = randomPos + quat * ((Vector3.forward + Vector3.right) * offset );
// test de l'impact sur le sol
createCube = Physics.Raycast (r, out hitCorner);
if (createCube) {
// vérification que le sol sous ce point est au même niveau que le sol sous le centre du cube
createCube = (hitCorner.point.y == hit.point.y && hitCorner.normal == Vector3.up);
}
// même test sous le centre du bord du cube
if (createCube) {
r.origin = randomPos + quat * (Vector3.forward * offset);
createCube = Physics.Raycast (r, out hitCorner);
if (createCube) {
// vérification que le sol sous le centre de ce bord est au même niveau que le sol sous le centre du cube
createCube = (hitCorner.point.y == hit.point.y && hitCorner.normal == Vector3.up);
}
}
// un coin ou un bord n'est pas valide, on sort de la boucle
if (!createCube)
break;
}
if (createCube) {
// tout est ok, on peut ajouter un cube à cette position
cubesData.Add (new CubeData (hit.point,size,randomAngle,diagonale));
total++;
}
}
}
}
if (createCube) {
// on avance de la moitié de la taille du dernier cube créé
pos.z += size * 0.5f;
} else {
// on avance de la moitié de la taille maximale possible
pos.z += endSize * 0.5f;
}
}
pos.x += moveStep;
}
// réduction de la taille max de création de cube pour la passe suivante
endSize -= sizeStep;
}
// mélange aléatoire de la liste
int lastIndex = cubesData.Count - 1;
for(int i=0;i<=lastIndex;i++){
CubeData temp = cubesData [i];
int index = Random.Range (0, lastIndex);
cubesData [i] = cubesData [index];
cubesData [index] = temp;
}
now = Time.realtimeSinceStartup - now;
Debug.Log (total+" cubes / "+(now).ToString("f2")+" s / "+processed+" operations");
}
void OnDrawGizmos(){
if (!cubeMesh)
return;
Quaternion quat = Quaternion.identity;
for (int i = 0, l = Mathf.RoundToInt(cubesData.Count*coverage); i < l; i++) {
Gizmos.color = Color.red;
quat.eulerAngles = new Vector3 (0, cubesData[i].angle, 0);
Gizmos.DrawWireMesh (cubeMesh, cubesData [i].position + Vector3.up*cubesData [i].size*0.5f, quat,Vector3.one*cubesData [i].size);
}
}
}