using System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening; using Yarn.Unity; using AibisDream; [System.Serializable] public class ParallaxLayerConfig { public GameObject layer; // 背景层的游戏对象 [SerializeField, Range(0.01f, 1f)] public float scrollSpeed; // 每层的滚动速度 public float scrollOffset; // 每层的滚动偏移量 } public class ParallaxManager : MonoBehaviour { public float speedMultiplier = 1; // 加速系数 public ParallaxLayerConfig[] layerConfigs; // 背景层配置数组 public ParallaxObjectCategoryConfig[] categoryConfigs; // 多种物体配置类别 public GameObject tunnelSprite; // 隧道的 Sprite 对象 public SpriteRenderer sisterSpriteRender; // 隧道的 Sprite 对象 public Vector3 tunnelEnterPosition; // 隧道进入位置 public Vector3 tunnelExitPosition; // 隧道退出位置 public Vector3 tunnelKeepPosition; // 隧道退出位置 public float tunnelDuration = 2f; // 隧道移动的时间 private bool isInTunnel = false; // 是否在隧道状态 private Dictionary> objectPools = new Dictionary>(); // 物体池 private float previousSpeedMultiplier; // 用于记录上一次的值 [YarnCommand("EnterTunnel")] public void EnterTunnel() { if (tunnelSprite != null && !isInTunnel) { tunnelSprite.transform.position = tunnelEnterPosition; isInTunnel = true; // 使用 DOTween 播放隧道进入动画 sisterSpriteRender.DOFade(0.4f, tunnelDuration); tunnelSprite.transform.DOMove(tunnelKeepPosition, tunnelDuration) .SetEase(Ease.Linear) .OnStart(() => Debug.Log("开始进入隧道")) .OnKill(() => { Debug.Log("已完成进入隧道"); ToggleCategoryVisibility("隧洞灯", true); }); } } [YarnCommand("ExitTunnel")] public void ExitTunnel() { if (tunnelSprite != null && isInTunnel) { isInTunnel = false; ToggleCategoryVisibility("隧洞灯", false); sisterSpriteRender.DOFade(0, tunnelDuration); // 使用 DOTween 播放隧道退出动画 tunnelSprite.transform.DOMove(tunnelExitPosition, tunnelDuration) .SetEase(Ease.Linear) .OnStart(() => Debug.Log("开始退出隧道")) .OnKill(() => { Debug.Log("已完成退出隧道"); ToggleCategoryVisibility("隧洞灯", false); tunnelSprite.transform.position = tunnelEnterPosition; }); } } void Update() { // 检测 speedMultiplier 是否被修改 if (!Mathf.Approximately(speedMultiplier, previousSpeedMultiplier)) { DOTween.timeScale = speedMultiplier; // 更新 DOTween 的全局时间缩放 previousSpeedMultiplier = speedMultiplier; // 记录当前值 Debug.Log($"运行时修改 DOTween.timeScale 为 {speedMultiplier}"); } if (Input.GetKeyDown(KeyCode.E)) { if (isInTunnel) { ExitTunnel(); } else { EnterTunnel(); } } } private void OnValidate() { // 当新速度值变化时,实时更新背景层和物体的速度 foreach (var config in layerConfigs) { StartLayerScroll(config); // 重新启动背景层滚动 } foreach (var category in categoryConfigs) { // 使用 TryGetValue 来安全地访问 objectPools 字典 if (objectPools.TryGetValue(category.categoryName, out var pool)) { ToggleCategoryVisibility(category.categoryName, category.isEnabled); // foreach (var obj in pool) // { // if (obj.activeInHierarchy) // { // StartObjectMoveAnimation(obj, category); // 重新启动物体移动动画 // } // } } } } void Start() { // 初始化记录的值 Color color = sisterSpriteRender.color; color.a = 0; sisterSpriteRender.color = color; previousSpeedMultiplier = speedMultiplier; DOTween.timeScale = speedMultiplier; // 确保启动时同步 Debug.Log($"初始化 DOTween.timeScale 为 {speedMultiplier}"); // 初始化背景层滚动 foreach (var config in layerConfigs) { StartLayerScroll(config); } // 初始化物体池并启动物体生成逻辑 foreach (var category in categoryConfigs) { InitializeObjectPool(category); if (category.useIntervalGeneration) { StartCoroutine(ManageObjectGeneration(category)); } else { // 按照距离生成 StartCoroutine(ManageObjectGenerationByDistance(category)); } } } // 初始化物体池 private void InitializeObjectPool(ParallaxObjectCategoryConfig categoryConfig) { List pool = new List(); for (int i = 0; i < categoryConfig.objects.Length; i++) { GameObject prefab = categoryConfig.objects[i]; GameObject obj = Instantiate(prefab, prefab.transform.position, Quaternion.identity, transform); pool.Add(obj); obj.SetActive(false); // 初始时禁用物体 } objectPools[categoryConfig.categoryName] = pool; Debug.Log($"为分类 {categoryConfig.categoryName} 初始化池子,池中物体数:{pool.Count}"); } // 开始背景层滚动 private void StartLayerScroll(ParallaxLayerConfig config) { Vector3 startPosition = config.layer.transform.position; float endPositionX = -config.scrollOffset; // 计算需要的时间,基于速度和距离 float distance = Mathf.Abs(endPositionX - startPosition.x); float duration = distance / config.scrollSpeed; // 使用速度来计算时间 // 背景层滚动时不受 speedMultiplier 影响 config.layer.transform.DOLocalMoveX(endPositionX, duration) .SetEase(Ease.Linear) .OnKill(() => ResetLayerPosition(config)); } // 重置背景层位置 private void ResetLayerPosition(ParallaxLayerConfig config) { Vector3 currentPosition = config.layer.transform.position; config.layer.transform.position = new Vector3(config.scrollOffset * 2, currentPosition.y, currentPosition.z); StartLayerScroll(config); } // 管理物体生成:按时间间隔生成 public IEnumerator ManageObjectGeneration(ParallaxObjectCategoryConfig categoryConfig) { if (!objectPools.ContainsKey(categoryConfig.categoryName)) { Debug.LogError($"没有为分类 {categoryConfig.categoryName} 初始化对象池!"); yield break; } var pool = objectPools[categoryConfig.categoryName]; int batchSize = categoryConfig.batchSize; // 每次生成的物体数量 float batchInterval = categoryConfig.batchInterval; // 每个物体之间的生成间隔 float delayAfterBatch = categoryConfig.delayAfterBatch; // 每组生成后的等待时间 // 启动首个物体 GameObject firstObject = pool[0]; if (!firstObject.activeInHierarchy) { firstObject.transform.position = firstObject.transform.position; // 保持编辑器中的初始位置 firstObject.SetActive(true); // 启用物体 StartObjectMoveAnimation(firstObject, categoryConfig); Debug.Log($"初始化并激活首个物体: {firstObject.name}, 位置: {firstObject.transform.position}"); ToggleCategoryVisibility(categoryConfig.categoryName, categoryConfig.isEnabled); } while (true) { // 连续生成多个物体 for (int i = 0; i < batchSize; i++) { GameObject obj = GetInactiveObjectFromPool(categoryConfig.categoryName); if (obj != null) { SpawnObject(obj, categoryConfig); Debug.Log($"生成物体: {obj.name}, 当前池大小: {pool.Count}"); } else { Debug.LogWarning($"对象池 {categoryConfig.categoryName} 中没有可用物体!可能需要增加池中物体数量。"); } // 每个物体之间的间隔 yield return new WaitForSeconds(Random.Range(batchInterval - 1f, batchInterval + 1f) / speedMultiplier); // 使用 speedMultiplier 来控制生成速率 } // 等待一段时间后再开始下一组生成 yield return new WaitForSeconds( Random.Range(delayAfterBatch - 1f, delayAfterBatch + 1.5f) / speedMultiplier); // 使用 speedMultiplier 来控制生成间隔 } } // 管理物体生成(基于距离) public IEnumerator ManageObjectGenerationByDistance(ParallaxObjectCategoryConfig categoryConfig) { var pool = objectPools[categoryConfig.categoryName]; GameObject firstObject = pool[0]; if (!firstObject.activeInHierarchy) { firstObject.transform.position = categoryConfig.objects[0].transform.position; firstObject.SetActive(true); // 启用物体 StartObjectMoveAnimation(firstObject, categoryConfig); ToggleCategoryVisibility(categoryConfig.categoryName, categoryConfig.isEnabled); Debug.Log($"生成了第一个物体: {firstObject.name}, 位置: {firstObject.transform.position}"); } GameObject lastObject = firstObject; while (true) { // 检查是否需要生成新物体 if (lastObject == null || lastObject.transform.position.x < -categoryConfig.generationThreshold) { GameObject obj = GetInactiveObjectFromPool(categoryConfig.categoryName); if (obj != null) { SpawnObject(obj, categoryConfig); lastObject = obj; } } yield return null; } } // 获取池中未激活的物体 private GameObject GetInactiveObjectFromPool(string categoryName) { if (objectPools.TryGetValue(categoryName, out var pool)) { // 筛选出所有未激活的对象 var inactiveObjects = pool.FindAll(o => !o.activeInHierarchy); if (inactiveObjects.Count > 0) { // 从未激活的对象中随机选择一个 return inactiveObjects[Random.Range(0, inactiveObjects.Count)]; } } return null; // 如果没有未激活的对象 } // 生成物体逻辑 private void SpawnObject(GameObject obj, ParallaxObjectCategoryConfig categoryConfig) { float xStart = categoryConfig.xSpawnRange; obj.transform.localPosition = new Vector3(xStart, obj.transform.localPosition.y, obj.transform.localPosition.z); obj.SetActive(true); ToggleCategoryVisibility(categoryConfig.categoryName, categoryConfig.isEnabled); StartObjectMoveAnimation(obj, categoryConfig); Debug.Log($"生成物体 {obj.name} 并开始移动, 初始位置: {obj.transform.position}"); } // 启动物体移动动画 private void StartObjectMoveAnimation(GameObject obj, ParallaxObjectCategoryConfig categoryConfig) { // 计算目标位置 Vector3 targetPosition = new Vector3(-categoryConfig.xSpawnRange, obj.transform.position.y, obj.transform.position.z); // 计算当前位置与目标位置的水平距离 float distance = Mathf.Abs(obj.transform.position.x - targetPosition.x); // 计算动画的持续时间:距离 / 速度 float duration = distance / categoryConfig.speed; // 执行动画 obj.transform.DOMoveX(targetPosition.x, duration).SetEase(Ease.Linear) .OnKill(() => HandleObjectCompletion(obj, categoryConfig)); } // 物体移动完成后处理 private void HandleObjectCompletion(GameObject obj, ParallaxObjectCategoryConfig categoryConfig) { obj.SetActive(false); // 隐藏物体 Debug.Log($"物体 {obj.name} 移动完成,回收到池中"); } // 控制分类的显示与隐藏 public void ToggleCategoryVisibility(string categoryName, bool isEnabled) { var category = System.Array.Find(categoryConfigs, config => config.categoryName == categoryName); if (category != null) { category.isEnabled = isEnabled; // 遍历该分类的所有物体,并根据 isEnabled 控制它们的显示状态 foreach (var obj in objectPools[categoryName]) { // 检查父物体是否有 SpriteRenderer var renderer = obj.GetComponent(); // 如果父物体没有 SpriteRenderer,检查所有子物体 if (renderer == null) { var childRenderers = obj.GetComponentsInChildren(); if (childRenderers.Length > 0) { // 如果找到子物体的 SpriteRenderer,修改它们的显示状态 foreach (var childRenderer in childRenderers) { childRenderer.enabled = isEnabled; } } else { // 如果父物体和子物体都没有 SpriteRenderer,输出警告 Debug.LogWarning($"物体 {obj.name} 及其子物体均未找到 SpriteRenderer"); } } else { // 如果父物体有 SpriteRenderer,直接修改父物体的显示状态 renderer.enabled = isEnabled; } } Debug.Log($"分类 {categoryName} 已{(isEnabled ? "启用" : "禁用")}"); } else { Debug.LogWarning($"未找到名为 {categoryName} 的分类"); } } }