You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

209 lines
7.9 KiB
C#

using System.Collections;
using UnityEngine;
[ExecuteAlways] // 런타임뿐만 아니라 에디터에서도 코드가 실행되도록 설정
public class tree : MonoBehaviour
{
public GameObject branchPrefab;
public int maxDepth = 4;
public float branchAngle = 45f;
public float scaleReduction = 0.6f;
public float initialLength = 5f;
public int branchesPerLevel = 4;
[Header("Artistic Settings")]
public Color trunkColor = new Color(0.4f, 0.2f, 0f); // 나무 몸통 (갈색)
public Color leafColor = new Color(1f, 0.4f, 0.6f); // 가지 끝부분 (핑크/벚꽃 느낌)
[Range(0f, 1f)] public float lengthRandomness = 0.2f; // 길이 랜덤 (0~1)
[Range(0f, 45f)] public float angleRandomness = 20f; // 휘어짐 극대화를 위한 각도 랜덤
[Tooltip("에디터에서 색상 변경 등 값을 수정할 때 마다 나무 형태가 계속 바뀌지 않도록 시드를 고정")]
public int randomSeed = 12345;
[Header("Animation & Wind")]
public float growthDelay = 0.1f; // 가지 생성 대기 시간 (애니메이션 효과)
[Range(0f, 5f)] public float windStrength = 1.5f; // 바람의 세기
private GameObject branchContainer;
void Start()
{
// 런타임 시작 시 새롭게 나무 생성 (에디터에선 이미 OnValidate/Enable 등으로 생성되었는지 확인 후 처리)
if (Application.isPlaying)
{
GenerateTree();
}
}
void OnEnable()
{
// 씬 로드, 컴포넌트 활성화 시에도 나무 생성 (에디터 모드에서 나무 보이게 함)
if (!Application.isPlaying && branchContainer == null)
{
GenerateTree();
}
}
void OnValidate()
{
// 인스펙터 값이 변경될 때 바로 적용되도록 OnValidate에서 재생성 호출
#if UNITY_EDITOR
UnityEditor.EditorApplication.delayCall += () =>
{
if (this != null && !Application.isPlaying)
{
GenerateTree();
}
};
#endif
}
public void GenerateTree()
{
if (branchPrefab == null) return;
// 기존에 생성된 부분 지우기 (DestroyImmediate는 에디터 전용/안전하게 사용)
if (branchContainer != null)
{
DestroyImmediate(branchContainer);
}
// Hierarchy에 잔여 TreeContainer가 남아있을 수도 있으므로 이름으로 확인 후 지우기
Transform oldContainer = transform.Find("TreeContainer");
if (oldContainer != null)
{
DestroyImmediate(oldContainer.gameObject);
}
branchContainer = new GameObject("TreeContainer");
branchContainer.transform.position = transform.position;
branchContainer.transform.parent = this.transform;
// 인스펙터 값을 바꿀 때마다 나무가 춤추듯 바뀌는 현상 방지용 랜덤 시드 세팅
Random.InitState(randomSeed);
// 런타임(플레이 모드)일 경우 코루틴으로 애니메이션, 에디터일 경우 즉시(Instant) 생성
if (Application.isPlaying)
{
StartCoroutine(CreateBranchRoutine(transform.position, transform.rotation, initialLength, 0, branchContainer.transform));
}
else
{
CreateBranchInstant(transform.position, transform.rotation, initialLength, 0, branchContainer.transform);
}
}
// 런타임용 (애니메이션 동반)
IEnumerator CreateBranchRoutine(Vector3 pos, Quaternion rot, float length, int depth, Transform parentTransform)
{
if (depth >= maxDepth) yield break;
yield return new WaitForSeconds(growthDelay);
Transform pivot = CreateBranchCore(pos, rot, length, depth, parentTransform);
Vector3 endPoint = pos + rot * (Vector3.up * length);
float nextLength = length * scaleReduction * Random.Range(1f - lengthRandomness, 1f + lengthRandomness);
float angleStep = 360f / branchesPerLevel;
for (int i = 0; i < branchesPerLevel; i++)
{
Quaternion finalRot = CalculateRotation(rot, depth, i, angleStep);
StartCoroutine(CreateBranchRoutine(endPoint, finalRot, nextLength, depth + 1, pivot));
}
}
// 에디터용 (대기 시간 없이 즉시 전체 생성)
void CreateBranchInstant(Vector3 pos, Quaternion rot, float length, int depth, Transform parentTransform)
{
if (depth >= maxDepth) return;
Transform pivot = CreateBranchCore(pos, rot, length, depth, parentTransform);
Vector3 endPoint = pos + rot * (Vector3.up * length);
float nextLength = length * scaleReduction * Random.Range(1f - lengthRandomness, 1f + lengthRandomness);
float angleStep = 360f / branchesPerLevel;
for (int i = 0; i < branchesPerLevel; i++)
{
Quaternion finalRot = CalculateRotation(rot, depth, i, angleStep);
CreateBranchInstant(endPoint, finalRot, nextLength, depth + 1, pivot);
}
}
// 공통 실행 로직: 하나의 나뭇가지 생성 및 색상/크기 세팅
Transform CreateBranchCore(Vector3 pos, Quaternion rot, float length, int depth, Transform parentTransform)
{
GameObject pivot = new GameObject("Pivot_Depth_" + depth);
pivot.transform.position = pos;
pivot.transform.rotation = rot;
pivot.transform.parent = parentTransform;
BranchSway sway = pivot.AddComponent<BranchSway>();
sway.Init(depth, windStrength);
Vector3 centerPos = pos + rot * (Vector3.up * length * 0.5f);
GameObject branch = Instantiate(branchPrefab, centerPos, rot);
branch.transform.parent = pivot.transform;
Renderer rnd = branch.GetComponentInChildren<Renderer>();
if (rnd != null)
{
// 아티스틱: 깊이에 따라 색상 자연스럽게 섞기
float colorT = maxDepth > 1 ? (float)depth / (maxDepth - 1) : 1f;
rnd.material.color = Color.Lerp(trunkColor, leafColor, colorT);
}
// 동적 크기 조절: 깊이에 따라 두께 비례 적용
float thickness = 0.15f * (maxDepth - depth);
branch.transform.localScale = new Vector3(thickness, length, thickness);
return pivot.transform;
}
// 공통 실행 로직: 나뭇가지가 뻗어나갈 방향 계산
Quaternion CalculateRotation(Quaternion currentRot, int depth, int index, float angleStep)
{
float randTilt = Random.Range(-angleRandomness, angleRandomness);
float randSpin = Random.Range(-angleRandomness, angleRandomness);
Quaternion tilt = Quaternion.Euler(branchAngle + randTilt, 0, 0);
Quaternion spin = Quaternion.Euler(0, (index * angleStep) + (depth * 25f) + randSpin, 0);
return currentRot * spin * tilt;
}
}
// 바람에 흔들리는 효과를 담당하는 컴포넌트
[ExecuteAlways]
public class BranchSway : MonoBehaviour
{
private Quaternion initialLocalRotation;
private float speed = 1f;
private float amount = 2f;
private float offset;
public void Init(int depth, float windStrength)
{
initialLocalRotation = transform.localRotation;
offset = Random.Range(0f, 100f);
speed = 1f + Random.Range(0f, 0.5f);
amount = 2.5f * (depth + 1) * windStrength;
}
void Update()
{
float timeToUse = Time.time;
#if UNITY_EDITOR
if (!Application.isPlaying) {
// 에디터 모드에서는 Scene View나 Inspector 갱신 시 timeSinceStartup을 사용
timeToUse = (float)UnityEditor.EditorApplication.timeSinceStartup;
}
#endif
float angleX = Mathf.Sin(timeToUse * speed + offset) * amount;
float angleZ = Mathf.Cos(timeToUse * speed * 0.8f + offset) * amount;
transform.localRotation = initialLocalRotation * Quaternion.Euler(angleX, 0, angleZ);
}
}