538 lines
16 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|