659 lines
20 KiB
C#
659 lines
20 KiB
C#
using System.Collections;
|
||
using UnityEngine;
|
||
using DG.Tweening;
|
||
using DragonLi.Core;
|
||
|
||
/// <summary>
|
||
/// 地图碎片/拼图碎片 - 可飞向拼图台,支持玩家交互
|
||
/// </summary>
|
||
public class MapFragment : MonoBehaviour
|
||
{
|
||
[Header("碎片标识")]
|
||
[SerializeField] private int fragmentId = 0; // 碎片ID (0-8)
|
||
[SerializeField] private int correctSlotId = 0; // 正确的拼装区槽位ID
|
||
|
||
[Header("碎片模型(子物体)")]
|
||
[SerializeField] private GameObject[] fragmentModels; // 9个碎片子模型(按顺序0-8排列,赋值ID显示对应模型)
|
||
|
||
[Header("展示设置")]
|
||
[SerializeField] private float displayDuration = 2f; // 展示时长(右手上方)
|
||
[SerializeField] private float handDisplayHeight = 0.3f; // 右手上方高度
|
||
[SerializeField] private float handDisplayRotateSpeed = 90f; // 展示旋转速度
|
||
[SerializeField] private bool flyToHandForDisplay = true; // 是否飞到右手上方展示
|
||
|
||
[Header("飞行设置")]
|
||
[SerializeField] private float flyDuration = 1f; // 飞向目标时长
|
||
[SerializeField] private float flyHeight = 1f; // 飞行高度
|
||
|
||
[Header("目标设置")]
|
||
[SerializeField] private Transform mapTableTarget; // 地图台/拼图台目标位置
|
||
[SerializeField] private PuzzleTable puzzleTable; // 关联的拼图台
|
||
|
||
[Header("水洼效果(钟乳石事件)")]
|
||
[SerializeField] private bool isInWaterPuddle = false; // 是否在水洼中
|
||
[SerializeField] private Transform waterPuddle; // 水洼位置
|
||
[SerializeField] private float waterDepth = 0.05f; // 水下深度
|
||
[SerializeField] private float floatHeight = 0.02f; // 浮动高度
|
||
[SerializeField] private float floatSpeed = 1f; // 浮动速度
|
||
[SerializeField] private float rotateSpeed = 30f; // 水下旋转速度
|
||
[SerializeField] private bool enableWaterGlow = true; // 水下发光效果
|
||
[SerializeField] private Color waterGlowColor = new Color(0.6f, 0.8f, 1f); // 水下发光颜色
|
||
|
||
[Header("状态")]
|
||
[SerializeField] private bool inStorageZone = false; // 是否在预制区
|
||
[SerializeField] private bool isGrabbed = false; // 是否被抓取
|
||
|
||
[Header("特效")]
|
||
[SerializeField] private ParticleSystem displayEffect; // 展示特效
|
||
[SerializeField] private ParticleSystem waterRippleEffect; // 水涟漪特效
|
||
|
||
[Header("音效")]
|
||
[SerializeField] private string displaySound = "1.36"; // 展示音效
|
||
[SerializeField] private string flySound = "1.37"; // 飞行音效
|
||
[SerializeField] private string arriveSound = "1.38"; // 到达音效
|
||
[SerializeField] private string grabSound = "1.43"; // 抓取音效
|
||
[SerializeField] private string waterSplashSound = "1.44"; // 水花音效
|
||
|
||
private bool isFlying = false;
|
||
private bool isAnimating = false;
|
||
private Vector3 originalPosition;
|
||
private Renderer fragmentRenderer;
|
||
private Material fragmentMaterial;
|
||
|
||
// 全局静态变量:当前被抓取的碎片(限制每次只能抓取一个)
|
||
private static MapFragment currentlyGrabbedMapFragment = null;
|
||
|
||
// 触发器检测:右手是否在碎片范围内
|
||
private bool rightHandInTrigger = false;
|
||
|
||
public int FragmentId => fragmentId;
|
||
public int CorrectSlotId => correctSlotId;
|
||
public bool IsInStorageZone => inStorageZone;
|
||
public bool IsGrabbed => isGrabbed;
|
||
public bool IsCollected { get; private set; } = false; // 是否已被收集
|
||
|
||
private void Awake()
|
||
{
|
||
fragmentRenderer = GetComponent<Renderer>();
|
||
if (fragmentRenderer != null)
|
||
{
|
||
fragmentMaterial = fragmentRenderer.material;
|
||
}
|
||
originalPosition = transform.position;
|
||
}
|
||
|
||
private void Start()
|
||
{
|
||
// 如果在水洼中,启动水洼效果
|
||
if (isInWaterPuddle)
|
||
{
|
||
StartCoroutine(WaterPuddleEffect());
|
||
}
|
||
else if (!inStorageZone)
|
||
{
|
||
// 开始展示流程(非预制区碎片)
|
||
StartCoroutine(DisplayAndFly());
|
||
}
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
// 检测右手交互
|
||
if (!isFlying && !isGrabbed && !inStorageZone)
|
||
{
|
||
CheckRightHandInteraction();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 展示并飞向地图台
|
||
/// </summary>
|
||
private IEnumerator DisplayAndFly()
|
||
{
|
||
// 播放展示音效
|
||
if (!string.IsNullOrEmpty(displaySound))
|
||
{
|
||
GameManager.Ins.PlaySound2DRPC(displaySound);
|
||
}
|
||
|
||
// 播放展示特效
|
||
if (displayEffect != null)
|
||
{
|
||
displayEffect.Play();
|
||
}
|
||
|
||
Debug.Log($"地图碎片开始展示 {displayDuration} 秒...");
|
||
|
||
// 展示等待
|
||
yield return new WaitForSeconds(displayDuration);
|
||
|
||
// 飞向地图台
|
||
FlyToMapTable();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 飞向地图台
|
||
/// </summary>
|
||
private void FlyToMapTable()
|
||
{
|
||
if (isFlying) return;
|
||
isFlying = true;
|
||
|
||
// 播放飞行音效
|
||
if (!string.IsNullOrEmpty(flySound))
|
||
{
|
||
GameManager.Ins.PlaySound2DRPC(flySound);
|
||
}
|
||
|
||
Debug.Log("地图碎片开始飞向地图台...");
|
||
|
||
// 获取目标位置
|
||
Vector3 targetPos = mapTableTarget != null ? mapTableTarget.position : Vector3.zero;
|
||
|
||
// 计算中间点(抛物线)
|
||
Vector3 startPos = transform.position;
|
||
Vector3 midPos = (startPos + targetPos) / 2f + Vector3.up * flyHeight;
|
||
|
||
// 使用DOPath创建抛物线路径
|
||
Vector3[] path = new Vector3[] { startPos, midPos, targetPos };
|
||
|
||
transform.DOPath(path, flyDuration, PathType.CatmullRom)
|
||
.SetEase(Ease.OutQuad)
|
||
.OnComplete(() => {
|
||
OnArrivedAtMapTable();
|
||
});
|
||
|
||
// 同时缩小
|
||
transform.DOScale(0.5f, flyDuration);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 到达地图台
|
||
/// </summary>
|
||
private void OnArrivedAtMapTable()
|
||
{
|
||
// 播放到达音效
|
||
if (!string.IsNullOrEmpty(arriveSound))
|
||
{
|
||
GameManager.Ins.PlaySound2DRPC(arriveSound);
|
||
}
|
||
|
||
Debug.Log("地图碎片已到达地图台!");
|
||
|
||
// 销毁地图碎片
|
||
StartCoroutine(Disappear());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 消失
|
||
/// </summary>
|
||
private IEnumerator Disappear()
|
||
{
|
||
// 缩小消失
|
||
transform.DOScale(Vector3.zero, 0.5f);
|
||
|
||
yield return new WaitForSeconds(0.5f);
|
||
|
||
Destroy(gameObject);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置地图台目标位置
|
||
/// </summary>
|
||
public void SetMapTableTarget(Transform target)
|
||
{
|
||
mapTableTarget = target;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置展示时长
|
||
/// </summary>
|
||
public void SetDisplayDuration(float duration)
|
||
{
|
||
displayDuration = duration;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置碎片ID
|
||
/// </summary>
|
||
public void SetFragmentId(int id)
|
||
{
|
||
fragmentId = id;
|
||
UpdateVisibleModel();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据fragmentId显示对应的子模型
|
||
/// 隐藏所有模型,只显示ID对应的模型
|
||
/// </summary>
|
||
public void UpdateVisibleModel()
|
||
{
|
||
// 隐藏所有碎片模型
|
||
if (fragmentModels != null)
|
||
{
|
||
foreach (var model in fragmentModels)
|
||
{
|
||
if (model != null)
|
||
{
|
||
model.SetActive(false);
|
||
}
|
||
}
|
||
|
||
// 只显示对应ID的模型
|
||
if (fragmentId >= 0 && fragmentId < fragmentModels.Length)
|
||
{
|
||
if (fragmentModels[fragmentId] != null)
|
||
{
|
||
fragmentModels[fragmentId].SetActive(true);
|
||
Debug.Log($"碎片 {fragmentId} 显示模型: {fragmentModels[fragmentId].name}");
|
||
|
||
// 更新渲染器引用(用于水洼发光效果)
|
||
fragmentRenderer = fragmentModels[fragmentId].GetComponent<Renderer>();
|
||
if (fragmentRenderer != null)
|
||
{
|
||
fragmentMaterial = fragmentRenderer.material;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"碎片ID {fragmentId} 超出模型数组范围 (0-{fragmentModels.Length - 1})");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning("碎片模型数组未设置!");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化碎片(设置ID、拼图台、显示模型)
|
||
/// 如果 flyToHandForDisplay = true,会飞到玩家右手上方旋转展示
|
||
/// </summary>
|
||
public void Initialize(int id, PuzzleTable table)
|
||
{
|
||
puzzleTable = table;
|
||
SetFragmentId(id);
|
||
correctSlotId = id; // 默认槽位ID等于碎片ID
|
||
|
||
// 飞到右手上方展示(猴子投篮、宝箱密码任务获取的碎片)
|
||
if (flyToHandForDisplay && !isInWaterPuddle)
|
||
{
|
||
FlyToPlayerHandForDisplay();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 飞到玩家右手上方展示
|
||
/// </summary>
|
||
private void FlyToPlayerHandForDisplay()
|
||
{
|
||
RightHand rightHand = GameManager.Ins?.playerRightHand;
|
||
if (rightHand == null)
|
||
{
|
||
Debug.LogWarning("未找到玩家右手,碎片直接飞向拼图台!");
|
||
// 直接飞向拼图台
|
||
if (puzzleTable != null)
|
||
{
|
||
puzzleTable.FlyFragmentToStorage(this);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 播放展示音效
|
||
if (!string.IsNullOrEmpty(displaySound))
|
||
{
|
||
GameManager.Ins.PlaySound2DRPC(displaySound);
|
||
}
|
||
|
||
// 播放展示特效
|
||
if (displayEffect != null)
|
||
{
|
||
displayEffect.Play();
|
||
}
|
||
|
||
Debug.Log($"碎片 {fragmentId} 飞向右手上方展示...");
|
||
|
||
// 飞到右手上方位置
|
||
Vector3 handPos = rightHand.transform.position + Vector3.up * handDisplayHeight;
|
||
|
||
// 先飞到右手上方(缩放动画)
|
||
transform.DOMove(handPos, 0.5f).SetEase(Ease.OutBack);
|
||
transform.DOScale(1.2f, 0.5f).SetEase(Ease.OutBack);
|
||
|
||
// 开始跟随右手并旋转展示
|
||
StartCoroutine(DisplayAboveHand(rightHand));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在右手上方跟随并旋转展示
|
||
/// </summary>
|
||
private IEnumerator DisplayAboveHand(RightHand hand)
|
||
{
|
||
float elapsed = 0f;
|
||
|
||
while (elapsed < displayDuration && hand != null)
|
||
{
|
||
// 跟随右手位置(上方一定高度)
|
||
Vector3 targetPos = hand.transform.position + Vector3.up * handDisplayHeight;
|
||
transform.position = targetPos;
|
||
|
||
// 旋转展示
|
||
transform.Rotate(Vector3.up, handDisplayRotateSpeed * Time.deltaTime);
|
||
|
||
elapsed += Time.deltaTime;
|
||
yield return null;
|
||
}
|
||
|
||
Debug.Log($"碎片 {fragmentId} 展示完成,飞向拼图台...");
|
||
|
||
// 播放飞行音效
|
||
if (!string.IsNullOrEmpty(flySound))
|
||
{
|
||
GameManager.Ins.PlaySound2DRPC(flySound);
|
||
}
|
||
|
||
// 展示完成,飞向拼图台预制区
|
||
if (puzzleTable != null)
|
||
{
|
||
puzzleTable.FlyFragmentToStorage(this);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置正确槽位ID
|
||
/// </summary>
|
||
public void SetCorrectSlotId(int id)
|
||
{
|
||
correctSlotId = id;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置拼图台引用
|
||
/// </summary>
|
||
public void SetPuzzleTable(PuzzleTable table)
|
||
{
|
||
puzzleTable = table;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置是否在预制区
|
||
/// </summary>
|
||
public void SetInStorageZone(bool inStorage)
|
||
{
|
||
inStorageZone = inStorage;
|
||
|
||
if (inStorage)
|
||
{
|
||
// 停止水洼效果
|
||
isInWaterPuddle = false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置水洼位置
|
||
/// </summary>
|
||
public void SetWaterPuddle(Transform puddle)
|
||
{
|
||
waterPuddle = puddle;
|
||
isInWaterPuddle = true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 水洼效果 - 碎片在水洼中的浮动和旋转
|
||
/// </summary>
|
||
private IEnumerator WaterPuddleEffect()
|
||
{
|
||
Debug.Log("拼图碎片开始水洼效果...");
|
||
|
||
// 应用水下发光效果
|
||
if (enableWaterGlow && fragmentMaterial != null)
|
||
{
|
||
if (fragmentMaterial.HasProperty("_EmissionColor"))
|
||
{
|
||
fragmentMaterial.EnableKeyword("_EMISSION");
|
||
fragmentMaterial.SetColor("_EmissionColor", waterGlowColor * 0.5f);
|
||
}
|
||
}
|
||
|
||
// 初始位置(沉入水中)
|
||
Vector3 basePosition = originalPosition;
|
||
if (waterPuddle != null)
|
||
{
|
||
basePosition = waterPuddle.position + Vector3.down * waterDepth;
|
||
}
|
||
|
||
transform.position = basePosition;
|
||
|
||
// 水下浮动和旋转循环
|
||
while (isInWaterPuddle && !isFlying && !isGrabbed)
|
||
{
|
||
// 浮动动画
|
||
float floatOffset = Mathf.Sin(Time.time * floatSpeed) * floatHeight;
|
||
Vector3 floatPosition = basePosition + Vector3.up * floatOffset;
|
||
|
||
if (!isAnimating)
|
||
{
|
||
transform.position = floatPosition;
|
||
}
|
||
|
||
// 缓慢旋转
|
||
transform.Rotate(Vector3.up, rotateSpeed * Time.deltaTime);
|
||
|
||
// 发光脉冲
|
||
if (enableWaterGlow && fragmentMaterial != null)
|
||
{
|
||
float glowPulse = Mathf.Sin(Time.time * 2f) * 0.3f + 0.5f;
|
||
if (fragmentMaterial.HasProperty("_EmissionColor"))
|
||
{
|
||
fragmentMaterial.SetColor("_EmissionColor", waterGlowColor * glowPulse);
|
||
}
|
||
}
|
||
|
||
yield return null;
|
||
}
|
||
|
||
// 结束水洼效果时恢复
|
||
if (fragmentMaterial != null && fragmentMaterial.HasProperty("_EmissionColor"))
|
||
{
|
||
fragmentMaterial.SetColor("_EmissionColor", Color.black);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检测右手交互
|
||
/// 条件:触发器检测右手进入 + (扳机或鼠标左键) + 当前没有其他碎片被抓取
|
||
/// </summary>
|
||
private void CheckRightHandInteraction()
|
||
{
|
||
// 必须右手在触发器范围内
|
||
if (!rightHandInTrigger) return;
|
||
|
||
RightHand rightHand = FindObjectOfType<RightHand>();
|
||
if (rightHand == null) return;
|
||
|
||
// 检测扳机或鼠标左键
|
||
bool triggerPressed = rightHand.isTrigger;
|
||
|
||
#if UNITY_EDITOR
|
||
// 编辑器模式:鼠标左键模拟扳机
|
||
triggerPressed = triggerPressed || Input.GetMouseButton(0);
|
||
#endif
|
||
|
||
// 每次只能抓取一个碎片
|
||
if (triggerPressed && currentlyGrabbedMapFragment == null && !isGrabbed)
|
||
{
|
||
OnGrabbed(rightHand);
|
||
currentlyGrabbedMapFragment = this;
|
||
}
|
||
|
||
// 检测松手(扳机和鼠标左键都释放)
|
||
if (isGrabbed)
|
||
{
|
||
bool triggerReleased = !rightHand.isTrigger;
|
||
|
||
#if UNITY_EDITOR
|
||
triggerReleased = triggerReleased && !Input.GetMouseButton(0);
|
||
#endif
|
||
|
||
if (triggerReleased)
|
||
{
|
||
OnReleased();
|
||
}
|
||
}
|
||
}
|
||
|
||
#region 触发器检测
|
||
|
||
private void OnTriggerEnter(Collider other)
|
||
{
|
||
// 检测右手碰触
|
||
if (!isGrabbed && !isFlying)
|
||
{
|
||
RightHand rightHand = other.GetComponent<RightHand>();
|
||
if (rightHand != null)
|
||
{
|
||
rightHandInTrigger = true;
|
||
Debug.Log($"右手进入MapFragment {fragmentId} 触发器范围");
|
||
}
|
||
}
|
||
}
|
||
|
||
private void OnTriggerExit(Collider other)
|
||
{
|
||
// 右手离开触发器范围
|
||
if (!isGrabbed && !isFlying)
|
||
{
|
||
RightHand rightHand = other.GetComponent<RightHand>();
|
||
if (rightHand != null)
|
||
{
|
||
rightHandInTrigger = false;
|
||
Debug.Log($"右手离开MapFragment {fragmentId} 触发器范围");
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
/// <summary>
|
||
/// 松手时清除全局抓取状态
|
||
/// </summary>
|
||
private void OnReleased()
|
||
{
|
||
if (currentlyGrabbedMapFragment == this)
|
||
{
|
||
currentlyGrabbedMapFragment = null;
|
||
}
|
||
isGrabbed = false;
|
||
Debug.Log($"拼图碎片 {fragmentId} 松手");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 被抓取
|
||
/// </summary>
|
||
private void OnGrabbed(RightHand hand)
|
||
{
|
||
isGrabbed = true;
|
||
isInWaterPuddle = false;
|
||
|
||
// 播放抓取音效
|
||
if (!string.IsNullOrEmpty(grabSound))
|
||
{
|
||
GameManager.Ins?.PlaySound2DRPC(grabSound);
|
||
}
|
||
|
||
// 播放水花效果(如果从水中抓取)
|
||
if (waterRippleEffect != null)
|
||
{
|
||
waterRippleEffect.transform.position = transform.position;
|
||
waterRippleEffect.Play();
|
||
}
|
||
|
||
// 播放水花音效
|
||
if (!string.IsNullOrEmpty(waterSplashSound))
|
||
{
|
||
GameManager.Ins?.PlaySound2DRPC(waterSplashSound);
|
||
}
|
||
|
||
// 从水中升起动画
|
||
if (isAnimating)
|
||
{
|
||
isAnimating = false;
|
||
transform.DOKill();
|
||
}
|
||
|
||
// 短暂升起效果
|
||
transform.DOMoveY(transform.position.y + 0.1f, 0.2f).SetEase(Ease.OutBack);
|
||
|
||
Debug.Log($"拼图碎片 {fragmentId} 被抓取");
|
||
|
||
// 飞向拼图台预制区
|
||
if (puzzleTable != null)
|
||
{
|
||
StartCoroutine(FlyToPuzzleTableAfterDelay(0.5f));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 延迟飞向拼图台
|
||
/// </summary>
|
||
private IEnumerator FlyToPuzzleTableAfterDelay(float delay)
|
||
{
|
||
yield return new WaitForSeconds(delay);
|
||
|
||
if (puzzleTable != null)
|
||
{
|
||
puzzleTable.FlyFragmentToStorage(this);
|
||
isGrabbed = false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 飞向拼图台预制区(由PuzzleTable调用)
|
||
/// </summary>
|
||
public void FlyToPuzzleTableStorage()
|
||
{
|
||
if (puzzleTable == null)
|
||
{
|
||
Debug.LogWarning("未设置拼图台!");
|
||
return;
|
||
}
|
||
|
||
puzzleTable.FlyFragmentToStorage(this);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 手动触发飞行(用于外部调用)
|
||
/// </summary>
|
||
public void TriggerFlyToTarget()
|
||
{
|
||
if (isFlying) return;
|
||
|
||
if (puzzleTable != null)
|
||
{
|
||
FlyToPuzzleTableStorage();
|
||
}
|
||
else
|
||
{
|
||
FlyToMapTable();
|
||
}
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
// 清理DOTween动画
|
||
transform.DOKill();
|
||
}
|
||
|
||
private void OnDrawGizmosSelected()
|
||
{
|
||
// 绘制水洼范围
|
||
if (isInWaterPuddle && waterPuddle != null)
|
||
{
|
||
Gizmos.color = new Color(0.2f, 0.6f, 0.8f, 0.3f);
|
||
Gizmos.DrawSphere(waterPuddle.position, 0.2f);
|
||
Gizmos.DrawLine(transform.position, waterPuddle.position);
|
||
}
|
||
}
|
||
}
|