using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif // 왼쪽 끝으로 이동한 배경을 오른쪽 끝으로 재배치하는 스크립트 [ExecuteAlways] public class BackgroundLoop : MonoBehaviour { public Sprite[] backgroundSprites; // 순서대로 교체할 공항 배경 스프라이트 public bool useResourceBackground = true; // Resources 폴더의 맵 배경을 자동으로 사용 public bool fitToMainCamera = true; // 맵 배경을 카메라 화면 크기에 맞춰 표시 public float cameraFitPadding = 1f; // 가장자리 빈틈 방지용 배경 확대 여유 public string resourceBackgroundPath = ""; // 맵 데이터가 없을 때 사용할 Resources 배경 public int startSpriteIndex = 0; // 이 배경 오브젝트가 시작할 스프라이트 번호 public int spriteAdvanceOnLoop = 2; // 배경 오브젝트가 두 장이므로 재배치될 때 두 칸씩 진행 private static string[] activeResourceBackgroundPaths = new string[0]; private static int activeBackgroundLoopCount = 1; private static float activeCameraFitScaleMultiplier = 1f; private static float activeVerticalOffset = 0f; private float width; // 배경의 가로 길이 private int currentSpriteIndex; // 현재 표시 중인 스프라이트 번호 private SpriteRenderer spriteRenderer; // 배경을 표시하는 스프라이트 렌더러 private BoxCollider2D backgroundCollider; // 루프 폭 계산용 콜라이더 private Sprite[] originalBackgroundSprites; // 맵 배경 리소스가 없을 때 되돌릴 기본 배경 private bool originalBackgroundSpritesCached = false; private Vector3 originalLocalPosition; // 배경 루프를 초기 배치로 되돌릴 때 사용할 위치 private Vector3 originalLocalScale = Vector3.one; // 자동 맞춤 해제 시 되돌릴 기본 스케일 private bool originalTransformCached = false; public static void ApplyResourceBackground(string resourcePath) { ApplyResourceBackground(string.IsNullOrEmpty(resourcePath) ? new string[0] : new string[] { resourcePath }); } public static void ApplyResourceBackground(string[] resourcePaths) { ApplyResourceBackground(resourcePaths, 1f, 0f); } public static void ApplyResourceBackground(string[] resourcePaths, float cameraFitScaleMultiplier, float verticalOffset) { activeResourceBackgroundPaths = NormalizeResourcePaths(resourcePaths); activeCameraFitScaleMultiplier = Mathf.Max(0.1f, cameraFitScaleMultiplier); activeVerticalOffset = verticalOffset; BackgroundLoop[] backgroundLoops = FindObjectsByType(FindObjectsSortMode.None); for(int i = 0; i < backgroundLoops.Length; i++) { if(backgroundLoops[i] != null) { backgroundLoops[i].InitializeBackground(); } } AlignLoopPositions(backgroundLoops); AssignSequentialSpriteIndices(backgroundLoops); } private void Awake() { InitializeBackground(); } private void OnEnable() { InitializeBackground(); } private void OnValidate() { InitializeBackground(); } private void Update() { if(!Application.isPlaying) { return; } // 현재 위치가 원점에서 왼쪽으로 width 이상 이동했을때 위치를 리셋 if(transform.position.x <= -width) { Reposition(); } } private void InitializeBackground() { CacheComponents(); CacheOriginalBackgroundSprites(); CacheOriginalTransform(); EnsureResourceBackground(); currentSpriteIndex = NormalizeSpriteIndex(startSpriteIndex); ApplyBackgroundSprite(currentSpriteIndex); FitSpriteToCamera(); SyncColliderSize(); RefreshWidth(); } private void CacheComponents() { if(spriteRenderer == null) { spriteRenderer = GetComponent(); } if(backgroundCollider == null) { backgroundCollider = GetComponent(); } } private void EnsureResourceBackground() { bool hasActiveResourceBackground = activeResourceBackgroundPaths != null && activeResourceBackgroundPaths.Length > 0; if(!useResourceBackground && !hasActiveResourceBackground) { return; } string[] finalResourcePaths = hasActiveResourceBackground ? activeResourceBackgroundPaths : NormalizeResourcePaths(string.IsNullOrEmpty(resourceBackgroundPath) ? new string[0] : new string[] { resourceBackgroundPath }); if(finalResourcePaths.Length == 0) { backgroundSprites = originalBackgroundSprites; return; } Sprite[] resourceSprites = LoadResourceBackgroundSprites(finalResourcePaths); if(resourceSprites.Length == 0) { return; } if(SpriteArraysMatch(backgroundSprites, resourceSprites)) { return; } backgroundSprites = resourceSprites; } private Sprite[] LoadResourceBackgroundSprites(string[] resourcePaths) { Sprite[] loadedSprites = new Sprite[resourcePaths.Length]; int loadedCount = 0; for(int i = 0; i < resourcePaths.Length; i++) { Sprite resourceSprite = LoadResourceBackgroundSprite(resourcePaths[i]); if(resourceSprite == null) { continue; } loadedSprites[loadedCount] = resourceSprite; loadedCount++; } if(loadedCount == loadedSprites.Length) { return loadedSprites; } Sprite[] compactSprites = new Sprite[loadedCount]; for(int i = 0; i < loadedCount; i++) { compactSprites[i] = loadedSprites[i]; } return compactSprites; } private Sprite LoadResourceBackgroundSprite(string resourcePath) { Sprite resourceSprite = Resources.Load(resourcePath); if(resourceSprite != null) { return resourceSprite; } #if UNITY_EDITOR string assetPath = "Assets/Resources/" + resourcePath + ".png"; Sprite editorSprite = AssetDatabase.LoadAssetAtPath(assetPath); if(editorSprite != null) { return editorSprite; } Texture2D texture = AssetDatabase.LoadAssetAtPath(assetPath); if(texture != null) { return Sprite.Create(texture, new Rect(0f, 0f, texture.width, texture.height), new Vector2(0.5f, 0.5f), 8f); } #endif return null; } private void CacheOriginalBackgroundSprites() { if(originalBackgroundSpritesCached) { return; } originalBackgroundSprites = backgroundSprites; originalBackgroundSpritesCached = true; } private void CacheOriginalTransform() { if(originalTransformCached) { return; } originalLocalPosition = transform.localPosition; originalLocalScale = transform.localScale; originalTransformCached = true; } private void RefreshWidth() { if(spriteRenderer != null && spriteRenderer.sprite != null) { width = spriteRenderer.bounds.size.x; return; } if(backgroundCollider != null) { width = backgroundCollider.size.x * Mathf.Abs(transform.lossyScale.x); return; } width = 0f; } private void SyncColliderSize() { if(spriteRenderer == null || spriteRenderer.sprite == null || backgroundCollider == null) { return; } Vector2 spriteSize = spriteRenderer.sprite.bounds.size; backgroundCollider.size = spriteSize; backgroundCollider.offset = Vector2.zero; backgroundCollider.isTrigger = true; } private void FitSpriteToCamera() { if(spriteRenderer == null || spriteRenderer.sprite == null) { return; } if(!fitToMainCamera) { transform.localScale = originalLocalScale; ApplyVerticalOffset(); return; } Camera targetCamera = Camera.main; if(targetCamera == null || !targetCamera.orthographic) { ApplyVerticalOffset(); return; } Vector2 spriteSize = spriteRenderer.sprite.bounds.size; if(spriteSize.x <= 0f || spriteSize.y <= 0f) { ApplyVerticalOffset(); return; } float cameraHeight = targetCamera.orthographicSize * 2f; float cameraWidth = cameraHeight * GetCameraAspect(targetCamera); float parentScaleX = transform.parent != null ? Mathf.Abs(transform.parent.lossyScale.x) : 1f; float parentScaleY = transform.parent != null ? Mathf.Abs(transform.parent.lossyScale.y) : 1f; float scaleX = cameraWidth / (spriteSize.x * Mathf.Max(parentScaleX, 0.0001f)); float scaleY = cameraHeight / (spriteSize.y * Mathf.Max(parentScaleY, 0.0001f)); float fitScale = Mathf.Max(scaleX, scaleY) * Mathf.Max(cameraFitPadding, 1f) * activeCameraFitScaleMultiplier; float signX = Mathf.Sign(originalLocalScale.x); float signY = Mathf.Sign(originalLocalScale.y); if(Mathf.Approximately(signX, 0f)) { signX = 1f; } if(Mathf.Approximately(signY, 0f)) { signY = 1f; } transform.localScale = new Vector3(fitScale * signX, fitScale * signY, originalLocalScale.z); ApplyVerticalOffset(); } private void ApplyVerticalOffset() { Vector3 localPosition = transform.localPosition; localPosition.y = originalLocalPosition.y + activeVerticalOffset; transform.localPosition = localPosition; } // 위치를 리셋하는 메서드 private void Reposition() { Vector2 offset = new Vector2(width * 2f, 0); transform.position = (Vector2) transform.position + offset; currentSpriteIndex = NormalizeSpriteIndex(currentSpriteIndex + GetSpriteAdvanceOnLoop()); ApplyBackgroundSprite(currentSpriteIndex); FitSpriteToCamera(); SyncColliderSize(); RefreshWidth(); } private int NormalizeSpriteIndex(int index) { if(backgroundSprites == null || backgroundSprites.Length == 0) { return 0; } int normalizedIndex = index % backgroundSprites.Length; if(normalizedIndex < 0) { normalizedIndex += backgroundSprites.Length; } return normalizedIndex; } private void ApplyBackgroundSprite(int index) { if(spriteRenderer == null || backgroundSprites == null || backgroundSprites.Length == 0) { return; } Sprite backgroundSprite = backgroundSprites[index]; if(backgroundSprite != null) { spriteRenderer.enabled = true; spriteRenderer.sprite = backgroundSprite; spriteRenderer.color = Color.white; } } private void SetCurrentSpriteIndex(int index) { currentSpriteIndex = NormalizeSpriteIndex(index); ApplyBackgroundSprite(currentSpriteIndex); FitSpriteToCamera(); SyncColliderSize(); RefreshWidth(); } private float GetCurrentWidth() { if(width > 0f) { return width; } RefreshWidth(); return width; } private int GetSpriteAdvanceOnLoop() { if(backgroundSprites != null && backgroundSprites.Length > 1 && activeBackgroundLoopCount > 1) { return activeBackgroundLoopCount; } return Mathf.Max(1, spriteAdvanceOnLoop); } private static float GetCameraAspect(Camera targetCamera) { if(targetCamera.aspect > 0f) { return targetCamera.aspect; } if(Screen.height > 0) { return (float) Screen.width / Screen.height; } return 16f / 9f; } private static string[] NormalizeResourcePaths(string[] resourcePaths) { if(resourcePaths == null || resourcePaths.Length == 0) { return new string[0]; } string[] normalizedPaths = new string[resourcePaths.Length]; int pathCount = 0; for(int i = 0; i < resourcePaths.Length; i++) { if(string.IsNullOrEmpty(resourcePaths[i])) { continue; } normalizedPaths[pathCount] = resourcePaths[i]; pathCount++; } if(pathCount == normalizedPaths.Length) { return normalizedPaths; } string[] compactPaths = new string[pathCount]; for(int i = 0; i < pathCount; i++) { compactPaths[i] = normalizedPaths[i]; } return compactPaths; } private static bool SpriteArraysMatch(Sprite[] firstSprites, Sprite[] secondSprites) { if(firstSprites == null || secondSprites == null || firstSprites.Length != secondSprites.Length) { return false; } for(int i = 0; i < firstSprites.Length; i++) { if(firstSprites[i] != secondSprites[i]) { return false; } } return true; } private static void AlignLoopPositions(BackgroundLoop[] backgroundLoops) { if(backgroundLoops == null || backgroundLoops.Length <= 1) { activeBackgroundLoopCount = 1; return; } int validCount = 0; for(int i = 0; i < backgroundLoops.Length; i++) { if(backgroundLoops[i] != null) { validCount++; } } if(validCount <= 1) { activeBackgroundLoopCount = Mathf.Max(1, validCount); return; } activeBackgroundLoopCount = validCount; BackgroundLoop[] sortedLoops = new BackgroundLoop[validCount]; int sortedIndex = 0; for(int i = 0; i < backgroundLoops.Length; i++) { if(backgroundLoops[i] != null) { sortedLoops[sortedIndex] = backgroundLoops[i]; sortedIndex++; } } System.Array.Sort(sortedLoops, (first, second) => first.originalLocalPosition.x.CompareTo(second.originalLocalPosition.x)); float loopWidth = sortedLoops[0].GetCurrentWidth(); if(loopWidth <= 0f) { return; } float baseX = sortedLoops[0].originalLocalPosition.x; for(int i = 0; i < sortedLoops.Length; i++) { Vector3 localPosition = sortedLoops[i].originalLocalPosition; localPosition.x = baseX + loopWidth * i; sortedLoops[i].transform.localPosition = localPosition; } } private static void AssignSequentialSpriteIndices(BackgroundLoop[] backgroundLoops) { if(backgroundLoops == null || backgroundLoops.Length == 0) { return; } int validCount = 0; for(int i = 0; i < backgroundLoops.Length; i++) { if(backgroundLoops[i] != null) { validCount++; } } if(validCount == 0) { return; } BackgroundLoop[] sortedLoops = new BackgroundLoop[validCount]; int sortedIndex = 0; for(int i = 0; i < backgroundLoops.Length; i++) { if(backgroundLoops[i] != null) { sortedLoops[sortedIndex] = backgroundLoops[i]; sortedIndex++; } } System.Array.Sort(sortedLoops, (first, second) => first.originalLocalPosition.x.CompareTo(second.originalLocalPosition.x)); for(int i = 0; i < sortedLoops.Length; i++) { sortedLoops[i].SetCurrentSpriteIndex(i); } } }