Files
Travel_Run/Assets/Scripts/BackgroundLoop.cs
T
2026-07-09 02:50:48 +09:00

538 lines
16 KiB
C#

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<BackgroundLoop>(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<SpriteRenderer>();
}
if(backgroundCollider == null)
{
backgroundCollider = GetComponent<BoxCollider2D>();
}
}
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<Sprite>(resourcePath);
if(resourceSprite != null)
{
return resourceSprite;
}
#if UNITY_EDITOR
string assetPath = "Assets/Resources/" + resourcePath + ".png";
Sprite editorSprite = AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
if(editorSprite != null)
{
return editorSprite;
}
Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>(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);
}
}
}