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