Files
MRArcadia/Assets/_Arcadia/Scripts/Item/MonkeyBasketTask.cs
2026-04-01 15:53:13 +08:00

598 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections;
using UnityEngine;
using DG.Tweening;
using DragonLi.Core;
using Random = UnityEngine.Random;
/// <summary>
/// 猴子投篮任务管理器
/// 玩家走到指定点,开启猴子投篮任务
/// 猴子出现并介绍,介绍完后拿竹篮
/// 玩家右手生成桃子,按扳机键投篮
/// 投进5个桃子后任务完成
/// </summary>
public class MonkeyBasketTask : MonoBehaviour
{
[Header("任务设置")]
[SerializeField] private int targetScore = 5; // 目标分数
[SerializeField] private float peachRegenDelay = 1f; // 桃子重新生成延迟
[Header("猴子设置")]
[SerializeField] private GameObject monkeyPrefab; // 猴子预制体
[SerializeField] private AudioClip monkeyIntroAudio; // 猴子介绍音频
[SerializeField] private string monkeyIntroText = "嗨!我是小猴子,快来帮我投篮进篮子里吧!"; // 猴子介绍文字
[SerializeField] private Transform monkeySpawnPosition; // 猴子生成位置
[Header("竹篮设置")]
//[SerializeField] private GameObject basketPrefab; // 竹篮预制体
private GameObject currentBasket; // 当前竹篮实例
[Header("桃子设置")]
[SerializeField] private GameObject peachPrefab; // 桃子预制体
private GameObject currentPeach; // 当前手中的桃子
private bool hasPeach = false; // 是否有桃子
[Header("UI设置")]
[SerializeField] private Transform uiAnchor; // UI锚点
[SerializeField] private GameObject dialogBoxPrefab; // 对话框预制体
private GameObject currentDialogBox; // 当前对话框
[Header("拼图碎片奖励")]
[SerializeField] private GameObject mapFragmentPrefab; // 碎片预制体(包含所有碎片子模型)
[SerializeField] private PuzzleTable puzzleTable; // 拼图台引用
[SerializeField] private Transform fragmentSpawnPoint; // 碎片生成位置(可选,默认猴子位置)
[Header("投篮区域")]
[SerializeField] private float throwDistance = 5f; // 投篮检测距离
// 任务状态
private MonkeyTaskState currentState = MonkeyTaskState.None;
private GameObject currentMonkey; // 当前猴子实例
private MonkeyNPCBehavior monkeyBehavior; // 猴子行为组件
private int currentScore = 0; // 当前分数
private RightHand playerRightHand; // 玩家右手引用
private bool isAimingAtBasket = false; // 是否瞄准竹篮
// 投篮计时器
private Coroutine peachRegenCoroutine;
// 扳机按住检测
private float triggerPressStartTime = 0f;
private bool isTriggerPressed = false;
/// <summary>
/// 任务状态枚举
/// </summary>
private enum MonkeyTaskState
{
None, // 未开始
MonkeyAppearing, // 猴子出现中
MonkeyIntroducing, // 猴子介绍中
MonkeyReady, // 猴子准备好(拿竹篮)
PlayerShooting, // 玩家投篮中
TaskComplete // 任务完成
}
// 事件
public event Action<int> OnScoreChanged; // 分数变化事件
public event Action OnTaskStarted; // 任务开始事件
public event Action OnTaskCompleted; // 任务完成事件
private void Update()
{
if (currentState == MonkeyTaskState.PlayerShooting)
{
CheckAimAtBasket();
// 检测扳机/鼠标按住时长
UpdateTriggerDetection();
}
}
/// <summary>
/// 更新扳机/鼠标按住检测
/// 记录按下和释放的时间,计算按住时长
/// 支持VR右扳机和PC鼠标左键
/// </summary>
private void UpdateTriggerDetection()
{
// 获取输入状态VR扳机或鼠标左键
bool isPressed = MRInput.Ins.pressRightTrigger || Input.GetMouseButton(0);
// 检测按下(开始计时)
if (isPressed && !isTriggerPressed)
{
triggerPressStartTime = Time.time;
isTriggerPressed = true;
Debug.Log("开始蓄力...");
}
// 检测释放(根据按住时长投掷)
if (!isPressed && isTriggerPressed)
{
float holdTime = Time.time - triggerPressStartTime;
isTriggerPressed = false;
Debug.Log($"释放!按住时长: {holdTime:F2}秒");
// 释放时执行投掷
ExecuteThrow(holdTime);
}
}
/// <summary>
/// 执行投掷(带力度)
/// </summary>
/// <param name="holdTime">按住扳机的时长</param>
private void ExecuteThrow(float holdTime)
{
if (currentState != MonkeyTaskState.PlayerShooting)
return;
if (!hasPeach || currentPeach == null)
return;
// 设置投掷力度
ThrowPeach throwPeach = currentPeach.GetComponent<ThrowPeach>();
if (throwPeach != null)
{
// 设置右手引用
throwPeach.SetHandReference(playerRightHand.transform);
throwPeach.SetThrowPower(holdTime);
throwPeach.Throw();
}
hasPeach = false;
currentPeach = null;
// 1秒后重新生成桃子
if (peachRegenCoroutine != null)
{
StopCoroutine(peachRegenCoroutine);
}
peachRegenCoroutine = StartCoroutine(RegenPeachAfterDelay(peachRegenDelay));
}
/// <summary>
/// 当桃子投失时调用(掉出边界或未进篮)
/// 确保玩家能继续获得新桃子
/// </summary>
public void OnPeachMissed()
{
hasPeach = false;
currentPeach = null;
// 1秒后重新生成桃子
if (peachRegenCoroutine != null)
{
StopCoroutine(peachRegenCoroutine);
}
peachRegenCoroutine = StartCoroutine(RegenPeachAfterDelay(peachRegenDelay));
Debug.Log("桃子投失,稍后生成新桃子...");
}
/// <summary>
/// 开始猴子投篮任务
/// </summary>
public void StartTask()
{
if (currentState != MonkeyTaskState.None)
{
Debug.LogWarning("任务已在进行中!");
return;
}
if (playerRightHand == null)
{
playerRightHand = GameManager.Ins.playerRightHand;
if (playerRightHand != null)
{
// 注册扳机事件
MRInput.Ins.RegisterClickRrightTrigger(OnRightTriggerClicked);
}
}
currentState = MonkeyTaskState.MonkeyAppearing;
OnTaskStarted?.Invoke();
Debug.Log("猴子投篮任务开始!");
// 第一步:猴子出现
SpawnMonkey();
}
/// <summary>
/// 生成猴子
/// </summary>
private void SpawnMonkey()
{
if (monkeyPrefab == null)
{
Debug.LogError("猴子预制体未设置!");
return;
}
Vector3 spawnPos = monkeySpawnPosition != null ?
monkeySpawnPosition.position :
GameLocal.Ins.self.transform.position + GameLocal.Ins.self.transform.forward * 3f;
currentMonkey = Instantiate(monkeyPrefab, spawnPos, Quaternion.identity);
currentMonkey.name = "MonkeyBasketNPC";
// 获取或添加猴子行为组件
monkeyBehavior = currentMonkey.GetComponent<MonkeyNPCBehavior>();
if (monkeyBehavior == null)
{
monkeyBehavior = currentMonkey.AddComponent<MonkeyNPCBehavior>();
}
// 设置猴子行为
monkeyBehavior.Setup(monkeyIntroAudio, monkeyIntroText);
// 注册猴子介绍完成事件
monkeyBehavior.OnIntroductionComplete += OnMonkeyIntroductionComplete;
// 开始猴子介绍
StartCoroutine(DelayedStartMonkeyIntroduction());
currentBasket = monkeyBehavior.basketPrefab;
// 添加竹篮碰撞检测组件
Basket basketComponent = currentBasket.GetComponent<Basket>();
if (basketComponent == null)
{
basketComponent = currentBasket.AddComponent<Basket>();
}
basketComponent.Setup(this);
}
/// <summary>
/// 延迟开始猴子介绍(确保初始化完成)
/// </summary>
private IEnumerator DelayedStartMonkeyIntroduction()
{
yield return new WaitForSeconds(0.2f);
if (monkeyBehavior != null)
{
monkeyBehavior.StartIntroduction();
currentState = MonkeyTaskState.MonkeyIntroducing;
}
}
/// <summary>
/// 猴子介绍完成回调
/// </summary>
private void OnMonkeyIntroductionComplete()
{
Debug.Log("猴子介绍完成,生成竹篮!");
currentState = MonkeyTaskState.MonkeyReady;
// 生成第一个桃子
SpawnPeachInHand();
// 进入投篮阶段
currentState = MonkeyTaskState.PlayerShooting;
}
/// <summary>
/// 在玩家右手生成桃子
/// </summary>
private void SpawnPeachInHand()
{
if (peachPrefab == null || playerRightHand == null)
{
Debug.LogError("桃子预制体或玩家右手未设置!");
return;
}
// 清理可能残留的旧桃子
CleanupOldPeaches();
// 在右手位置生成桃子
Vector3 peachPos = playerRightHand.transform.position;
currentPeach = Instantiate(peachPrefab, peachPos, playerRightHand.transform.rotation);
currentPeach.name = "BasketPeach";
// 设置为投篮专用桃子
ThrowPeach throwPeach = currentPeach.GetComponent<ThrowPeach>();
if (throwPeach == null)
{
throwPeach = currentPeach.AddComponent<ThrowPeach>();
}
throwPeach.Setup(this);
// 桃子作为右手的子物体
currentPeach.transform.SetParent(playerRightHand.transform);
currentPeach.transform.localPosition = Vector3.zero;
currentPeach.transform.localRotation = Quaternion.identity;
hasPeach = true;
Debug.Log("新桃子已生成在手上");
}
/// <summary>
/// 清理可能残留的旧桃子对象
/// </summary>
private void CleanupOldPeaches()
{
// 清理当前桃子引用
if (currentPeach != null)
{
Destroy(currentPeach);
currentPeach = null;
}
// 查找并销毁右手下可能残留的桃子
Transform rightHand = playerRightHand.transform;
for (int i = rightHand.childCount - 1; i >= 0; i--)
{
Transform child = rightHand.GetChild(i);
if (child.name == "BasketPeach")
{
Debug.Log($"清理残留桃子: {child.name}");
Destroy(child.gameObject);
}
}
hasPeach = false;
}
/// <summary>
/// 玩家按下扳机键保留兼容但实际使用Update中的检测
/// </summary>
private void OnRightTriggerClicked()
{
// 现在使用UpdateTriggerDetection()检测按住时长
// 这个方法保留是为了兼容性
}
/// <summary>
/// 投掷桃子(兼容旧接口,使用默认力度)
/// </summary>
public void ThrowPeach()
{
if (currentState != MonkeyTaskState.PlayerShooting)
return;
if (!hasPeach || currentPeach == null)
return;
// 使用默认力度投掷
ThrowPeach throwPeach = currentPeach.GetComponent<ThrowPeach>();
if (throwPeach != null)
{
throwPeach.Throw();
}
hasPeach = false;
currentPeach = null;
// 1秒后重新生成桃子
if (peachRegenCoroutine != null)
{
StopCoroutine(peachRegenCoroutine);
}
peachRegenCoroutine = StartCoroutine(RegenPeachAfterDelay(peachRegenDelay));
}
/// <summary>
/// 延迟后重新生成桃子
/// </summary>
private IEnumerator RegenPeachAfterDelay(float delay)
{
yield return new WaitForSeconds(delay);
if (currentState == MonkeyTaskState.PlayerShooting)
{
SpawnPeachInHand();
}
}
/// <summary>
/// 检查是否瞄准竹篮
/// </summary>
private void CheckAimAtBasket()
{
if (currentBasket == null || playerRightHand == null)
return;
// 计算射线方向
Vector3 rayOrigin = playerRightHand.transform.position;
Vector3 rayDirection = playerRightHand.transform.forward;
// 检测射线是否击中竹篮
Collider basketCollider = currentBasket.GetComponent<Collider>();
if (basketCollider != null)
{
float distanceToBasket = Vector3.Distance(rayOrigin, currentBasket.transform.position);
float dotProduct = Vector3.Dot(rayDirection.normalized,
(currentBasket.transform.position - rayOrigin).normalized);
// 如果瞄准角度足够且距离合适
isAimingAtBasket = dotProduct > 0.8f && distanceToBasket < throwDistance;
}
}
/// <summary>
/// 投篮得分
/// </summary>
public void ScoreBasket()
{
currentScore++;
OnScoreChanged?.Invoke(currentScore);
Debug.Log($"投篮得分! 当前分数: {currentScore}/{targetScore}");
// 检查是否完成任务
if (currentScore >= targetScore)
{
TaskCompleted();
}
}
/// <summary>
/// 任务完成
/// </summary>
private void TaskCompleted()
{
currentState = MonkeyTaskState.TaskComplete;
OnTaskCompleted?.Invoke();
Debug.Log("猴子投篮任务完成!");
// 清理
Cleanup();
// 显示完成提示
ShowCompletionMessage();
// 生成拼图碎片奖励
SpawnRewardFragment();
}
/// <summary>
/// 生成拼图碎片作为任务奖励
/// 从PuzzleTable获取随机碎片ID创建碎片并飞向拼图台
/// </summary>
private void SpawnRewardFragment()
{
if (puzzleTable == null || mapFragmentPrefab == null)
{
Debug.LogWarning("拼图台或碎片预制体未设置,无法生成碎片奖励!");
return;
}
// 从PuzzleTable获取随机碎片ID
int fragmentId = puzzleTable.GetRandomFragmentIdToCollect();
if (fragmentId < 0)
{
Debug.LogWarning("没有待收集的碎片可分配!");
return;
}
// 确定生成位置
Vector3 spawnPos = fragmentSpawnPoint != null ?
fragmentSpawnPoint.position :
(currentMonkey != null ? currentMonkey.transform.position + Vector3.up * 0.5f : transform.position + Vector3.up * 0.5f);
// 创建碎片实例
GameObject fragmentObj = Instantiate(mapFragmentPrefab, spawnPos, Quaternion.Euler(0, Random.Range(0, 360), 0));
MapFragment mapFragment = fragmentObj.GetComponent<MapFragment>();
if (mapFragment == null)
{
mapFragment = fragmentObj.AddComponent<MapFragment>();
}
// 初始化碎片设置ID、拼图台会自动飞向拼图台
mapFragment.Initialize(fragmentId, puzzleTable);
Debug.Log($"猴子投篮任务奖励碎片已生成碎片ID: {fragmentId}");
}
/// <summary>
/// 显示完成提示
/// </summary>
private void ShowCompletionMessage()
{
// 可以添加UI提示或音效
if (GameManager.Ins != null)
{
GameManager.Ins.PlaySound2DRPC("1.6"); // 播放成功音效
}
}
/// <summary>
/// 检查桃子是否投进竹篮
/// </summary>
public bool CheckPeachInBasket(Vector3 peachPosition)
{
if (currentBasket == null)
return false;
float distance = Vector3.Distance(peachPosition, currentBasket.transform.position);
float basketRadius = 0.5f; // 竹篮半径
return distance < basketRadius;
}
/// <summary>
/// 获取竹篮位置
/// </summary>
public Vector3 GetBasketPosition()
{
return currentBasket != null ? currentBasket.transform.position : Vector3.zero;
}
/// <summary>
/// 获取当前分数
/// </summary>
public int GetCurrentScore()
{
return currentScore;
}
/// <summary>
/// 获取目标分数
/// </summary>
public int GetTargetScore()
{
return targetScore;
}
/// <summary>
/// 清理资源
/// </summary>
private void Cleanup()
{
if (peachRegenCoroutine != null)
{
StopCoroutine(peachRegenCoroutine);
peachRegenCoroutine = null;
}
if (currentPeach != null)
{
Destroy(currentPeach);
currentPeach = null;
}
hasPeach = false;
}
/// <summary>
/// 重置任务
/// </summary>
public void ResetTask()
{
Cleanup();
if (currentBasket != null)
{
Destroy(currentBasket);
currentBasket = null;
}
if (currentMonkey != null)
{
Destroy(currentMonkey);
currentMonkey = null;
}
if (currentDialogBox != null)
{
Destroy(currentDialogBox);
currentDialogBox = null;
}
currentScore = 0;
currentState = MonkeyTaskState.None;
}
private void OnDestroy()
{
Cleanup();
}
}