using System.Collections; using System.Collections.Generic; using UnityEngine; public class BackgroundManager : MonoBehaviour { [System.Serializable] public class BackgroundLayer { public float yThreshold; public Sprite backgroundSprite; public float transitionDistance = 3f; // Distance over which the transition occurs [Range(0f, 1f)] public float parallaxEffect = 0.5f; } [SerializeField] private SpriteRenderer spriteRenderer; [SerializeField] private BackgroundLayer[] backgroundLayers; [SerializeField] private Transform playerTransform; // Add this field [SerializeField] private float tileBuffer = 1f; // Extra tiles beyond camera view private bool isTransitioning = false; // Add this field private Transform cameraTransform; private Vector3 lastCameraPosition; private int currentLayerIndex = 0; private int lastLayerIndex = 0; private float startPositionX; private float startPositionY; private float lastUpdatePlayerY = 0f; // Track the last Y position of the player private bool isMovingUp = false; // Track if the player is moving up private Dictionary backgroundTiles = new Dictionary(); private Dictionary transitionTiles = new Dictionary(); private Vector2 tileSize; private Camera mainCamera; private void Start() { mainCamera = Camera.main; cameraTransform = mainCamera.transform; lastCameraPosition = cameraTransform.position; if (spriteRenderer == null) spriteRenderer = GetComponent(); if (backgroundLayers != null && backgroundLayers.Length > 0) { Sprite firstSprite = backgroundLayers[0].backgroundSprite; tileSize = new Vector2(firstSprite.bounds.size.x, firstSprite.bounds.size.y); } // Set initial background based on player position if (backgroundLayers != null && backgroundLayers.Length > 0) { float playerY = playerTransform.position.y; int initialLayerIndex = 0; // Find the correct background layer for the player's position for (int i = 0; i < backgroundLayers.Length; i++) { if (playerY > backgroundLayers[i].yThreshold) { initialLayerIndex = i; break; } } spriteRenderer.sprite = backgroundLayers[initialLayerIndex].backgroundSprite; currentLayerIndex = initialLayerIndex; lastLayerIndex = currentLayerIndex; } InitializeInitialTiles(); startPositionX = playerTransform.position.x; startPositionY = playerTransform.position.y; } private void InitializeInitialTiles() { // Calculate visible area float cameraHeight = 2f * mainCamera.orthographicSize; float cameraWidth = cameraHeight * mainCamera.aspect; // Calculate number of tiles needed int horizontalTiles = Mathf.CeilToInt(cameraWidth / tileSize.x) + 2; int verticalTiles = Mathf.CeilToInt(cameraHeight / tileSize.y) + 2; // Create initial grid of tiles for (int x = -horizontalTiles; x <= horizontalTiles; x++) { for (int y = -verticalTiles; y <= verticalTiles; y++) { CreateTile(new Vector2Int(x, y)); } } } private void CreateTile(Vector2Int gridPosition) { if (backgroundTiles.ContainsKey(gridPosition)) return; GameObject tileObj = new GameObject($"Tile_{gridPosition.x}_{gridPosition.y}"); tileObj.transform.parent = transform; SpriteRenderer tileSR = tileObj.AddComponent(); tileSR.sprite = backgroundLayers[currentLayerIndex].backgroundSprite; tileSR.sortingOrder = spriteRenderer.sortingOrder; tileSR.sortingLayerName = spriteRenderer.sortingLayerName; // Position the tile Vector3 position = new Vector3( gridPosition.x * tileSize.x, gridPosition.y * tileSize.y, transform.position.z ); tileObj.transform.localPosition = position; tileObj.transform.localScale = Vector3.one; // Match scale backgroundTiles.Add(gridPosition, tileSR); } private void Update() { UpdateTiles(); // Calculate parallax movement Vector3 cameraDelta = cameraTransform.position - lastCameraPosition; Vector3 newPosition = transform.position; // Update X position with parallax float parallaxX = (cameraTransform.position.x - startPositionX) * GetCurrentParallaxEffect(); newPosition.x = startPositionX + parallaxX; // Update Y position to follow camera float parallaxY = (cameraTransform.position.y - startPositionY) * GetCurrentParallaxEffect(); newPosition.y = startPositionY + parallaxY; transform.position = newPosition; // Check for background change float currentY = playerTransform.position.y; int newLayerIndex = currentLayerIndex; for (int i = 0; i < backgroundLayers.Length; i++) { if (currentY > backgroundLayers[i].yThreshold) { newLayerIndex = i; break; } } if (newLayerIndex != currentLayerIndex && !isTransitioning) // Add transition check { StartCoroutine(TransitionBackground(backgroundLayers[newLayerIndex])); currentLayerIndex = newLayerIndex; } lastCameraPosition = cameraTransform.position; float lastUpdatePlayerYRounded = Mathf.Round(lastUpdatePlayerY * 10) / 10; float playerYRounded = Mathf.Round(playerTransform.position.y * 10) / 10; if (lastUpdatePlayerYRounded != playerYRounded) // Check if player has moved { if (lastUpdatePlayerYRounded > playerYRounded) // Check if player is moving down { isMovingUp = false; } else if (lastUpdatePlayerYRounded < playerYRounded) // Check if player is moving up { isMovingUp = true; } lastUpdatePlayerY = playerYRounded; // Update the last Y position } } private void UpdateTiles() { // Calculate visible area with buffer float cameraHeight = 2f * mainCamera.orthographicSize * (1f + tileBuffer); float cameraWidth = cameraHeight * mainCamera.aspect * (1f + tileBuffer); // Calculate parallax offset float parallaxX = (cameraTransform.position.x - startPositionX) * GetCurrentParallaxEffect(); float parallaxY = (cameraTransform.position.y - startPositionY) * GetCurrentParallaxEffect(); // Calculate grid positions that should be visible Vector2 cameraPos = mainCamera.transform.position; // Adjust the buffer based on parallax effect float parallaxBuffer = 1f; // Base buffer size float effectiveBuffer = parallaxBuffer * (1f + GetCurrentParallaxEffect()); int minX = Mathf.FloorToInt((parallaxX - (cameraWidth / 2 * effectiveBuffer)) / tileSize.x); int maxX = Mathf.CeilToInt((parallaxX + (cameraWidth / 2 * effectiveBuffer)) / tileSize.x); int minY = Mathf.FloorToInt((parallaxY - (cameraHeight / 2 * effectiveBuffer)) / tileSize.y); int maxY = Mathf.CeilToInt((parallaxY + (cameraHeight / 2 * effectiveBuffer)) / tileSize.y); // Create new tiles for (int x = minX; x <= maxX; x++) { for (int y = minY; y <= maxY; y++) { CreateTile(new Vector2Int(x, y)); } } // Remove out-of-view tiles List tilesToRemove = new List(); foreach (var tile in backgroundTiles) { if (tile.Key.x < minX - 1 || tile.Key.x > maxX + 1 || tile.Key.y < minY - 1 || tile.Key.y > maxY + 1) { tilesToRemove.Add(tile.Key); } } foreach (var tilePos in tilesToRemove) { if (!transitionTiles.ContainsKey(tilePos)) { Destroy(backgroundTiles[tilePos].gameObject); backgroundTiles.Remove(tilePos); } } } private float GetCurrentParallaxEffect() { if (backgroundLayers == null || backgroundLayers.Length == 0) return 0.5f; return backgroundLayers[currentLayerIndex].parallaxEffect; } private IEnumerator TransitionBackground(BackgroundLayer newLayer) { if (isTransitioning || newLayer.backgroundSprite.name == backgroundLayers[lastLayerIndex].backgroundSprite.name) yield break; isTransitioning = true; bool initialMovingUp = isMovingUp; float initialY = playerTransform.position.y; // Create transition renderers for all existing tiles foreach (var tile in backgroundTiles) { GameObject transObj = new GameObject($"TransitionTile_{tile.Key.x}_{tile.Key.y}"); transObj.transform.parent = transform; transObj.transform.localPosition = tile.Value.transform.localPosition + new Vector3(0, 0, -0.1f); transObj.transform.localScale = Vector3.one; SpriteRenderer transSR = transObj.AddComponent(); transSR.sprite = newLayer.backgroundSprite; transSR.sortingOrder = tile.Value.sortingOrder + 1; transSR.sortingLayerName = tile.Value.sortingLayerName; transSR.color = new Color(1, 1, 1, 0); transitionTiles.Add(tile.Key, transSR); } // Fade in new sprites over old ones while (true) { float currentY = playerTransform.position.y; // Check if direction changed if (initialMovingUp != isMovingUp && ((initialMovingUp ? initialY > currentY : initialY < currentY))) { foreach (var transTile in transitionTiles.Values) { Destroy(transTile.gameObject); } transitionTiles.Clear(); isTransitioning = false; yield break; } // Calculate alpha float alpha = Mathf.Abs(currentY - initialY) / newLayer.transitionDistance; Color newColor = new Color(1, 1, 1, Mathf.Clamp01(alpha)); // Update all transition tiles foreach (var transTile in transitionTiles.Values) { transTile.color = newColor; } if (alpha >= 1f) break; yield return null; } // Switch sprites on all tiles foreach (var tile in backgroundTiles.Values) { tile.sprite = newLayer.backgroundSprite; } // Cleanup transition tiles foreach (var transTile in transitionTiles.Values) { Destroy(transTile.gameObject); } transitionTiles.Clear(); isTransitioning = false; lastLayerIndex = currentLayerIndex; } }