598 lines
18 KiB
C#
598 lines
18 KiB
C#
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();
|
||
}
|
||
} |