using UnityEngine; namespace EerieVillage.Background { /// /// 무한 가로 스크롤 배경 — sprite 재활용 reposition 패턴. /// PD 지시 (2026-05-10): "리소스를 재활용할 수 있는 기능 — Tiled size 단순 키움 영역 X". /// /// 동작: /// - Start 시점 — sprite 가로 폭 측정 + 자식 사본 2개 (Left·Right) 영역 영역 영역 영역 영역. /// → 화면 영역 영역 영역 3 sprite (root + Left + Right) 영역 영역 영역 영역 영역. /// - LateUpdate — Camera 영역 영역 영역 영역 영역 sprite 폭 영역 영역 영역 영역 → root 영역 정수 배수 영역 reposition. /// → 자식 사본 영역 영역 영역 영역 따라가 영역 영역 영역 영역 영역 영역 영역 영역. /// /// 효율: /// - sprite 영역 1개 (Resources 영역 1회 영역 — Texture 메모리 영역 X·재사용) /// - GameObject 영역 3개 (root + 2 사본) — Camera 영역 영역 영역 영역 영역 영역 영역 충분. /// [RequireComponent(typeof(SpriteRenderer))] public class InfiniteHorizontalBackground : MonoBehaviour { [Tooltip("자식 사본 sortingOrder (root sortingOrder 영역 영역). 영역 영역 영역 영역 0 영역 영역 영역 영역.")] [SerializeField] int _childSortingOrderOffset = 0; Transform _camTr; float _spriteWidth; Transform _leftCopy; Transform _rightCopy; void Start() { var cam = Camera.main; if (cam == null) { Debug.LogWarning($"[InfiniteHorizontalBackground@{name}] Camera.main NULL — 영역 영역 영역."); enabled = false; return; } _camTr = cam.transform; var sr = GetComponent(); if (sr.sprite == null) { Debug.LogWarning($"[InfiniteHorizontalBackground@{name}] sprite NULL — 영역 영역 영역."); enabled = false; return; } // sprite 가로 영역 (월드 단위·scale 영역 영역) _spriteWidth = sr.sprite.bounds.size.x * transform.lossyScale.x; if (_spriteWidth <= 0.001f) { Debug.LogWarning($"[InfiniteHorizontalBackground@{name}] spriteWidth ~0 — 영역 영역 영역."); enabled = false; return; } // 자식 사본 2개 영역 영역 (Left·Right) _leftCopy = CreateCopy("Left", -_spriteWidth, sr); _rightCopy = CreateCopy("Right", +_spriteWidth, sr); } Transform CreateCopy(string copyName, float worldOffsetX, SpriteRenderer src) { var copy = new GameObject(copyName); copy.transform.SetParent(transform, false); // localPosition 영역 — parent.lossyScale 영역 영역 영역. 영역 영역 영역 — local 단위 영역 영역. float localOffsetX = worldOffsetX / transform.lossyScale.x; copy.transform.localPosition = new Vector3(localOffsetX, 0f, 0f); copy.transform.localScale = Vector3.one; var sr = copy.AddComponent(); sr.sprite = src.sprite; sr.sortingLayerID = src.sortingLayerID; sr.sortingOrder = src.sortingOrder + _childSortingOrderOffset; sr.color = src.color; sr.flipX = src.flipX; sr.flipY = src.flipY; return copy.transform; } void LateUpdate() { if (_camTr == null) return; // Camera 영역 영역 영역 영역 영역 영역 sprite 폭 영역 영역 영역 영역 → root 영역 정수 배수 영역 reposition float dx = _camTr.position.x - transform.position.x; if (Mathf.Abs(dx) >= _spriteWidth) { int n = Mathf.RoundToInt(dx / _spriteWidth); transform.position += new Vector3(n * _spriteWidth, 0f, 0f); } } } }