414 lines
12 KiB
C#
414 lines
12 KiB
C#
using DG.Tweening;
|
||
using Knife.Effects.SimpleController;
|
||
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using TMPro;
|
||
using UnityEngine;
|
||
using UnityEngine.AI;
|
||
using UnityEngine.UI;
|
||
|
||
|
||
public class AIController : MonoBehaviour
|
||
{
|
||
public AudioClip introAudio;//开场白音频
|
||
|
||
//动画参数
|
||
public string waveAnimationTrigger = "Wave";//打招呼动画触发器
|
||
public string leaveAnimationTrigger = "Leave";//离开动画触发器
|
||
|
||
//旋转过渡参数
|
||
public float rotationDuration = 1.5f;//旋转过渡时间
|
||
public float spawnDistance = 2f;//生成距离
|
||
public float rotationSmoothness = 5f;//旋转平滑度
|
||
|
||
//添加文字显示参数
|
||
public float charDisplayDelay = 0.5f;//每个字符显示的延迟时间
|
||
|
||
//介绍完成事件
|
||
public event Action OnIntroductionComplete;
|
||
|
||
private AudioSource audioSource;
|
||
private Animator animator;
|
||
private bool isIntroductionStarted = false;
|
||
private Transform playerTransform;
|
||
private Coroutine introductionCoroutine;
|
||
private Coroutine textDisplayCoroutine;
|
||
private bool hasIntroductionCompleted = false;//添加标志位
|
||
private bool isDuringIntroduction = false;//标记是否在介绍期间
|
||
|
||
|
||
//文本变量
|
||
public TMP_Text speedTxt;
|
||
private const float TxtSpeed = 1; //文字显示时间
|
||
public Image dialogBoxImage;//对话框图像
|
||
|
||
//特殊旋转角度
|
||
private Quaternion specialRotationOffset;//特殊旋转偏转
|
||
|
||
private void Awake()
|
||
{
|
||
audioSource = GetComponent<AudioSource>();
|
||
if (audioSource == null)
|
||
{
|
||
audioSource = gameObject.AddComponent<AudioSource>();
|
||
}
|
||
animator = GetComponent<Animator>();
|
||
//修改:设置特殊旋转偏移
|
||
specialRotationOffset = Quaternion.Euler(0, 90, 0);
|
||
}
|
||
|
||
private void Start()
|
||
{
|
||
FindPlayer();
|
||
|
||
//初始时隐藏文本
|
||
if (speedTxt != null)
|
||
{
|
||
speedTxt.gameObject.SetActive(false);
|
||
}
|
||
//初始时隐藏对话框
|
||
if (dialogBoxImage != null)
|
||
{
|
||
dialogBoxImage.gameObject.SetActive(false);
|
||
}
|
||
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
//在介绍期间实时面向玩家
|
||
if (isDuringIntroduction && playerTransform != null)
|
||
{
|
||
FacePlayerWithOffset();
|
||
}
|
||
}
|
||
|
||
//动态查找Player
|
||
private void FindPlayer()
|
||
{
|
||
//通过GameManager获取Player
|
||
if (GameManager.Ins != null && GameManager.Ins.player != null)
|
||
{
|
||
playerTransform = GameManager.Ins.player.transform;
|
||
Debug.Log("在GameManager中找到Player");
|
||
return;
|
||
}
|
||
|
||
//通过标签查找Player
|
||
GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
|
||
if (playerObj != null)
|
||
{
|
||
playerTransform = playerObj.transform;
|
||
Debug.Log("找到Player标签");
|
||
return;
|
||
}
|
||
|
||
//通过类型查找Player组件
|
||
Player playerController = FindObjectOfType<Player>();
|
||
if (playerController != null)
|
||
{
|
||
playerTransform = playerController.transform;
|
||
Debug.Log("找到player组件");
|
||
return;
|
||
}
|
||
|
||
Debug.LogError("Player没有找到,请重试");
|
||
StartCoroutine(RetryFindPlayer());
|
||
}
|
||
|
||
private IEnumerator RetryFindPlayer()
|
||
{
|
||
yield return new WaitForSeconds(1f);
|
||
FindPlayer();
|
||
}
|
||
public void StartIntroduction()
|
||
{
|
||
if (isIntroductionStarted) return;
|
||
|
||
//确保我们有Player引用
|
||
if (playerTransform == null)
|
||
{
|
||
FindPlayer();
|
||
if (playerTransform == null)
|
||
{
|
||
Debug.LogError("不能开始介绍,玩家参考是空的");
|
||
return;
|
||
}
|
||
}
|
||
|
||
isIntroductionStarted = true;
|
||
Debug.Log("AI介绍开始");
|
||
|
||
|
||
//停止任何现有的协程
|
||
if (introductionCoroutine != null)
|
||
{
|
||
StopCoroutine(introductionCoroutine);
|
||
}
|
||
|
||
//启动开场白协程
|
||
introductionCoroutine = StartCoroutine(IntroductionRoutine());
|
||
}
|
||
|
||
private IEnumerator IntroductionRoutine()
|
||
{
|
||
Debug.Log("开始介绍开场白");
|
||
isDuringIntroduction = true;
|
||
|
||
//初始面向玩家
|
||
FacePlayerWithOffset();
|
||
|
||
//应用特殊旋转
|
||
if (playerTransform != null)
|
||
{
|
||
//计算面向玩家的方向
|
||
Vector3 directionToPlayer = playerTransform.position - transform.position;
|
||
directionToPlayer.y = 0;//保持水平方向
|
||
if (directionToPlayer != Vector3.zero)
|
||
{
|
||
//先面向玩家
|
||
Quaternion lookAtPlayer = Quaternion.LookRotation(directionToPlayer);
|
||
//应用特殊旋转偏移
|
||
Quaternion targetRotation = lookAtPlayer * specialRotationOffset;
|
||
//平滑旋转到目标角度
|
||
yield return StartCoroutine(SmoothRotate(targetRotation, rotationDuration));
|
||
}
|
||
}
|
||
|
||
//播放打招呼动画
|
||
if (animator != null && !string.IsNullOrEmpty(waveAnimationTrigger))
|
||
{
|
||
|
||
animator.SetTrigger(waveAnimationTrigger);
|
||
Debug.Log("播放打招呼动画");
|
||
|
||
//同时播放开场白音频
|
||
if (introAudio != null)
|
||
{
|
||
//只在打招呼的时候显示文本
|
||
if (speedTxt != null)
|
||
{
|
||
speedTxt.gameObject.SetActive(true);
|
||
string fullText = "欢迎来到魔力队长的未来世界,宇宙的凶恶生物正源源不断的侵入我们的领地,让我们携手努力拯救这个世界,保护人们不再受到伤害!";
|
||
textDisplayCoroutine = StartCoroutine(DisplayTextCharByChar(fullText));
|
||
|
||
//等待文字开始显示后再播放声音
|
||
yield return new WaitForSeconds(0.5f);
|
||
}
|
||
|
||
Debug.Log("玩家音频播放:" + introAudio.name);
|
||
audioSource.clip = introAudio;
|
||
audioSource.Play();
|
||
|
||
//等待音频播放,但在结束前2秒隐藏文本
|
||
float audioLength = introAudio.length;
|
||
yield return new WaitForSeconds(audioLength - 2f);//提前两秒
|
||
|
||
//音频播放完毕后隐藏文本
|
||
if (speedTxt != null)
|
||
{
|
||
HideTxt();
|
||
}
|
||
|
||
//等待剩余的2秒音频播放时间
|
||
yield return new WaitForSeconds(2f);
|
||
|
||
Debug.Log("介绍音频完成");
|
||
}
|
||
else
|
||
{
|
||
//如果没有音频,等待12秒后隐藏文本
|
||
yield return new WaitForSeconds(10f);//减少等待时间,因为不需要等待音频
|
||
|
||
if (speedTxt != null)
|
||
{
|
||
HideTxt();
|
||
}
|
||
|
||
yield return new WaitForSeconds(2f);//再等待两秒
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError("AI身上没有介绍音频");
|
||
yield return new WaitForSeconds(3f);
|
||
}
|
||
|
||
//介绍结束,停止实时面向
|
||
isDuringIntroduction = false;
|
||
|
||
//播放离开动画
|
||
if (animator != null && !string.IsNullOrEmpty(leaveAnimationTrigger))
|
||
{
|
||
//等待一段时间后播放离开动画
|
||
yield return new WaitForSeconds(2f);
|
||
//旋转180度
|
||
Quaternion targetRotation = transform.rotation * Quaternion.Euler(0, 180, 0);
|
||
yield return StartCoroutine(SmoothRotate(targetRotation, rotationDuration));
|
||
|
||
animator.SetTrigger(leaveAnimationTrigger);
|
||
Debug.Log("播放离开动画");
|
||
|
||
//等待离开动画播放
|
||
yield return new WaitForSeconds(2f);
|
||
|
||
}
|
||
|
||
//触发介绍完成事件
|
||
hasIntroductionCompleted = true;
|
||
OnIntroductionComplete?.Invoke();
|
||
Debug.Log("AI介绍完成,准备离开");
|
||
|
||
//禁用AI角色
|
||
gameObject.SetActive(false);
|
||
|
||
}
|
||
|
||
//逐字显示文本的协程
|
||
private IEnumerator DisplayTextCharByChar(string fullText)
|
||
{
|
||
if (speedTxt == null) yield break;
|
||
|
||
//激活并显示对话框
|
||
if (dialogBoxImage != null)
|
||
{
|
||
dialogBoxImage.gameObject.SetActive(true);
|
||
//设置对话框初始透明度为0
|
||
dialogBoxImage.color = new Color(dialogBoxImage.color.r, dialogBoxImage.color.g, dialogBoxImage.color.b, 0f);
|
||
//对话框渐显
|
||
dialogBoxImage.DOFade(1f, TxtSpeed).SetEase(Ease.Linear);
|
||
}
|
||
|
||
//初始时设置文字的透明度为0(完全透明)
|
||
speedTxt.color = new Color(speedTxt.color.r, speedTxt.color.g, speedTxt.color.b, 0f);
|
||
|
||
//先渐显文本背景
|
||
speedTxt.DOFade(1f, TxtSpeed).SetEase(Ease.Linear);
|
||
yield return new WaitForSeconds(TxtSpeed);
|
||
|
||
//逐字显示文本
|
||
speedTxt.text = "";//清空文本
|
||
|
||
for (int i = 0; i < fullText.Length; i++)
|
||
{
|
||
speedTxt.text += fullText[i];
|
||
yield return new WaitForSeconds(charDisplayDelay);
|
||
}
|
||
}
|
||
|
||
//实时面向玩家
|
||
private void FacePlayerWithOffset()
|
||
{
|
||
if (playerTransform == null) return;
|
||
|
||
//计算面向玩家的方向
|
||
Vector3 directionToPlayer = playerTransform.position - transform.position;
|
||
directionToPlayer.y = 0;//保持水平方向
|
||
if (directionToPlayer != Vector3.zero)
|
||
{
|
||
//计算面向玩家的旋转
|
||
Quaternion lookRotation = Quaternion.LookRotation(directionToPlayer);
|
||
//应用特殊旋转偏移
|
||
Quaternion targetRotation = lookRotation * specialRotationOffset;
|
||
//平滑旋转到目标角度
|
||
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * rotationSmoothness);
|
||
}
|
||
}
|
||
|
||
//平滑旋转协程
|
||
private IEnumerator SmoothRotate(Quaternion targetRotation, float duration)
|
||
{
|
||
float elapsedTime = 0f;
|
||
Quaternion startRotation = transform.rotation;
|
||
|
||
while (elapsedTime < duration)
|
||
{
|
||
transform.rotation = Quaternion.Slerp(startRotation, targetRotation, elapsedTime / duration);
|
||
elapsedTime += Time.deltaTime;
|
||
yield return null;
|
||
}
|
||
|
||
//确保最终旋转到目标角度
|
||
transform.rotation = targetRotation;
|
||
}
|
||
|
||
public void StartTxt(string txt, Action cb = null)
|
||
{
|
||
if (speedTxt == null) return;
|
||
|
||
speedTxt.text = txt;
|
||
//初始时设置文字的透明度为0(完全透明)
|
||
speedTxt.color = new Color(speedTxt.color.r, speedTxt.color.g, speedTxt.color.b, 0f);
|
||
// 使用DoTween实现文字的渐显效果
|
||
speedTxt.DOFade(1f, TxtSpeed).SetEase(Ease.Linear).OnComplete(() =>
|
||
{
|
||
cb?.Invoke();
|
||
}); // 渐显到完全不透明,使用线性缓动效果
|
||
}
|
||
|
||
//隐藏文本的方法
|
||
public void HideTxt(Action cb = null)
|
||
{
|
||
if (speedTxt == null) return;
|
||
|
||
//停止任何正在进行的文本显示协程
|
||
if (textDisplayCoroutine != null)
|
||
{
|
||
StopCoroutine(textDisplayCoroutine);
|
||
}
|
||
|
||
//创建序列来同时隐藏文本和对话框
|
||
Sequence hideSequence = DOTween.Sequence();
|
||
|
||
//添加文本渐隐到序列
|
||
if (speedTxt != null)
|
||
{
|
||
hideSequence.Join(speedTxt.DOFade(0f, TxtSpeed).SetEase(Ease.Linear));
|
||
}
|
||
|
||
//添加对话框渐隐到序列
|
||
if (dialogBoxImage != null)
|
||
{
|
||
hideSequence.Join(dialogBoxImage.DOFade(0f, TxtSpeed).SetEase(Ease.Linear));
|
||
}
|
||
|
||
//序列完成后隐藏游戏对象并调用回调
|
||
hideSequence.OnComplete(() =>
|
||
{
|
||
if (speedTxt != null)
|
||
{
|
||
speedTxt.gameObject.SetActive(false);
|
||
}
|
||
if (dialogBoxImage != null)
|
||
{
|
||
dialogBoxImage.gameObject.SetActive(false);
|
||
}
|
||
cb?.Invoke();
|
||
});
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
//清理协程
|
||
if (introductionCoroutine != null)
|
||
{
|
||
StopCoroutine(introductionCoroutine);
|
||
}
|
||
|
||
if (textDisplayCoroutine != null)
|
||
{
|
||
StopCoroutine(textDisplayCoroutine);
|
||
}
|
||
|
||
// 清理所有Tween动画
|
||
if (speedTxt != null)
|
||
{
|
||
speedTxt.DOKill();
|
||
}
|
||
if (dialogBoxImage != null)
|
||
{
|
||
dialogBoxImage.DOKill();
|
||
}
|
||
}
|
||
} |