using UnityEngine;
using System.Collections;
public class 聊天邀请管理器 : MonoBehaviour
{
// 单例模式
public static 聊天邀请管理器 Instance { get; private set; }
[Header("引用")]
public 聊天邀请弹窗 邀请弹窗;
public 聊天系统 聊天系统;
public bool 初始处于外出模式 = false; // 默认不进入外出模式
[Header("邀请设置")]
public float 最小邀请间隔 = 10000f;
public float 最大邀请间隔 = 30000f;
public bool 调试模式 = true;
private bool 处于外出模式 = false;
private bool 正在聊天 = false;
private Coroutine 邀请协程;
private void Awake()
{
// 单例初始化
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject); // 可选:跨场景保持
}
else
{
Destroy(gameObject);
return;
}
}
public void 设置正在聊天(bool 聊天中)
{
正在聊天 = 聊天中;
if (调试模式)
Debug.Log($"聊天状态变更为: {聊天中}");
if (聊天中)
{
if (邀请协程 != null)
{
StopCoroutine(邀请协程);
邀请协程 = null;
}
}
else if (处于外出模式 && 邀请协程 == null)
{
邀请协程 = StartCoroutine(等待并邀请());
}
}
void Start()
{
// 验证引用
if (邀请弹窗 == null)
Debug.LogError("聊天邀请管理器:请赋值邀请弹窗!");
else
Debug.Log("聊天邀请管理器:已正确引用邀请弹窗");
// 自动查找聊天系统
if (聊天系统 == null)
{
聊天系统 = FindObjectOfType<聊天系统>();
if (聊天系统 == null)
Debug.LogError("聊天邀请管理器:未找到聊天系统组件!");
}
// 初始状态设置
处于外出模式 = 初始处于外出模式;
if (处于外出模式 && !正在聊天 && 邀请协程 == null)
{
邀请协程 = StartCoroutine(等待并邀请());
}
}
// 显示邀请
public void 显示邀请(string 开场白, System.Action 接受回调)
{
if (邀请弹窗 == null)
{
Debug.LogError("邀请弹窗未赋值,无法显示邀请");
return;
}
邀请弹窗.显示弹窗(
() =>
{
// 接受邀请回调
if (调试模式) Debug.Log("邀请被接受");
接受回调?.Invoke();
},
() =>
{
// 拒绝邀请回调
if (调试模式) Debug.Log("邀请被拒绝");
if (处于外出模式 && !正在聊天 && 邀请协程 == null)
{
邀请协程 = StartCoroutine(等待并邀请());
}
},
开场白 // 传入开场白用于弹窗显示
);
}
// 设置外出模式
public void 设置外出模式(bool 开启)
{
处于外出模式 = 开启;
if (调试模式)
Debug.Log($"外出模式已{(开启 ? "开启" : "关闭")}");
if (开启 && !正在聊天 && 邀请协程 == null)
{
邀请协程 = StartCoroutine(等待并邀请());
}
else if (!开启 && 邀请协程 != null)
{
StopCoroutine(邀请协程);
邀请协程 = null;
}
}
// 等待一段时间后显示邀请
private IEnumerator 等待并邀请()
{
while (true)
{
// 随机等待时间
float 等待时间 = Random.Range(最小邀请间隔, 最大邀请间隔);
if (调试模式)
Debug.Log($"将在 {等待时间:F1} 秒后显示下一次邀请");
yield return new WaitForSeconds(等待时间);
// 检查是否仍需要显示邀请
if (处于外出模式 && !正在聊天)
{
显示邀请("默认开场白", () =>
{
if (聊天系统 != null)
{
聊天系统.打开聊天窗口();
}
});
break; // 显示后退出协程,等待聊天结束后重新开始
}
else if (!处于外出模式 || 正在聊天)
{
break;
}
}
邀请协程 = null;
}
}
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using DG.Tweening;
using System;
using System.Collections;
using System.Collections.Generic;
public class 聊天邀请弹窗 : MonoBehaviour
{
[Header("UI元素")]
public GameObject 邀请窗口; // 弹窗根对象
public Button 接受按钮;
public Button 拒绝按钮;
public CanvasGroup 弹窗画布组;
public Text 标题文本;
public Text 内容文本;
public Text 倒计时文本;
public GameObject 聊天框;
[Header("配置")]
public float 超时时间 = 30f;
public float 动画时长 = 0.3f;
public float 按钮缩放比例 = 1.05f;
public string 默认标题 = "新的聊天邀请";
public string 默认内容 = "有人想与你聊天,是否接受?";
// 状态与队列管理
private bool 正在显示 = false;
private Coroutine 倒计时协程;
private Action 接受回调;
private Action 拒绝回调;
private Queue<弹窗数据> 弹窗队列 = new Queue<弹窗数据>();
// 弹窗数据结构
private struct 弹窗数据
{
public string 标题;
public string 内容;
public Action 接受回调;
public Action 拒绝回调;
}
void Awake()
{
// 确保对象激活(防止初始状态错误)
if (!gameObject.activeSelf)
gameObject.SetActive(true);
// 初始化UI状态(只隐藏视觉效果,保持对象激活)
初始化UI状态();
// 检查并修复UI组件
检查并修复UI组件();
// 绑定按钮事件
绑定按钮事件();
}
// 初始化UI状态(关键:保持对象激活)
private void 初始化UI状态()
{
if (邀请窗口 != null && !邀请窗口.activeSelf)
邀请窗口.SetActive(true);
if (弹窗画布组 != null)
{
弹窗画布组.alpha = 0;
弹窗画布组.interactable = false;
弹窗画布组.blocksRaycasts = false;
}
正在显示 = false;
}
// 检查并修复必要的UI组件
private void 检查并修复UI组件()
{
// 检查弹窗根对象
if (邀请窗口 == null)
{
Debug.LogError("聊天邀请弹窗:请赋值邀请窗口对象!");
return;
}
// 检查CanvasGroup
if (弹窗画布组 == null)
{
弹窗画布组 = 邀请窗口.GetComponent<CanvasGroup>();
if (弹窗画布组 == null)
{
弹窗画布组 = 邀请窗口.AddComponent<CanvasGroup>();
Debug.LogWarning("聊天邀请弹窗:自动添加了CanvasGroup组件");
}
}
// 检查并修复按钮
修复按钮(接受按钮, "接受按钮");
修复按钮(拒绝按钮, "拒绝按钮");
// 检查聊天框引用
if (聊天框 == null)
Debug.LogError("聊天邀请弹窗:请赋值聊天框对象!");
}
// 修复单个按钮(确保可交互)
private void 修复按钮(Button 按钮, string 按钮名称)
{
if (按钮 == null)
{
Debug.LogError($"聊天邀请弹窗:{按钮名称}未赋值!");
return;
}
// 确保按钮所在对象激活
if (!按钮.gameObject.activeSelf)
按钮.gameObject.SetActive(true);
// 确保按钮组件启用
按钮.enabled = true;
按钮.interactable = true;
// 确保有图像组件(用于接收点击)
if (按钮.GetComponent<Image>() == null)
{
Image img = 按钮.gameObject.AddComponent<Image>();
img.color = new Color(0, 0, 0, 0); // 透明背景
Debug.LogWarning($"聊天邀请弹窗:{按钮名称}缺少Image组件,已自动添加透明背景");
}
// 确保有EventTrigger组件
if (按钮.GetComponent<EventTrigger>() == null)
按钮.gameObject.AddComponent<EventTrigger>();
}
// 绑定按钮事件
private void 绑定按钮事件()
{
// 清除现有事件避免重复绑定
接受按钮?.onClick.RemoveAllListeners();
拒绝按钮?.onClick.RemoveAllListeners();
// 绑定点击事件
if (接受按钮 != null)
{
接受按钮.onClick.AddListener(On接受按钮点击);
初始化按钮交互(接受按钮);
}
if (拒绝按钮 != null)
{
拒绝按钮.onClick.AddListener(On拒绝按钮点击);
初始化按钮交互(拒绝按钮);
}
}
// 初始化按钮交互动画和事件
private void 初始化按钮交互(Button 按钮)
{
EventTrigger trigger = 按钮.GetComponent<EventTrigger>();
trigger.triggers.Clear();
// 鼠标进入事件
var enterEvent = new EventTrigger.Entry();
enterEvent.eventID = EventTriggerType.PointerEnter;
enterEvent.callback.AddListener((data) =>
{
Debug.Log($"{按钮.name}:鼠标进入");
按钮.transform.DOScale(按钮缩放比例, 0.15f);
});
trigger.triggers.Add(enterEvent);
// 鼠标离开事件
var exitEvent = new EventTrigger.Entry();
exitEvent.eventID = EventTriggerType.PointerExit;
exitEvent.callback.AddListener((data) =>
{
Debug.Log($"{按钮.name}:鼠标离开");
按钮.transform.DOScale(1f, 0.15f);
});
trigger.triggers.Add(exitEvent);
// 鼠标按下事件
var downEvent = new EventTrigger.Entry();
downEvent.eventID = EventTriggerType.PointerDown;
downEvent.callback.AddListener((data) =>
{
Debug.Log($"{按钮.name}:鼠标按下");
});
trigger.triggers.Add(downEvent);
}
// 显示弹窗(简化版)
public void 显示弹窗(Action 接受回调函数, Action 拒绝回调函数)
{
显示弹窗(默认标题, 默认内容, 接受回调函数, 拒绝回调函数);
}
// 显示弹窗(完整版)
public void 显示弹窗(string 标题, string 内容, Action 接受回调函数, Action 拒绝回调函数)
{
// 如果当前有弹窗显示,加入队列等待
if (正在显示)
{
弹窗队列.Enqueue(new 弹窗数据
{
标题 = 标题,
内容 = 内容,
接受回调 = 接受回调函数,
拒绝回调 = 拒绝回调函数
});
Debug.Log($"弹窗加入队列,当前队列长度:{弹窗队列.Count}");
return;
}
// 直接显示弹窗
内部显示弹窗(标题, 内容, 接受回调函数, 拒绝回调函数);
}
// 内部显示逻辑(核心修复:确保激活状态)
private void 内部显示弹窗(string 标题, string 内容, Action 接受回调函数, Action 拒绝回调函数)
{
// 保存回调
接受回调 = 接受回调函数;
拒绝回调 = 拒绝回调函数;
// 1. 确保整个弹窗层级处于激活状态
确保对象激活();
// 2. 设置文本内容
if (标题文本 != null) 标题文本.text = 标题;
if (内容文本 != null) 内容文本.text = 内容;
if (倒计时文本 != null) 倒计时文本.text = $"({超时时间}s)";
// 3. 重置画布组状态
弹窗画布组.alpha = 0;
弹窗画布组.interactable = true;
弹窗画布组.blocksRaycasts = true;
// 4. 播放显示动画
弹窗画布组.DOFade(1, 动画时长).SetEase(Ease.OutQuad);
邀请窗口.transform.localScale = Vector3.zero;
邀请窗口.transform.DOScale(1, 动画时长).SetEase(Ease.OutBack);
// 5. 安全启动协程(核心修复点)
启动倒计时协程();
正在显示 = true;
Debug.Log("弹窗显示成功");
}
// 确保对象处于激活状态
private void 确保对象激活()
{
// 确保脚本所在对象激活
if (!gameObject.activeSelf)
{
gameObject.SetActive(true);
Debug.Log("脚本所在对象已激活");
}
// 确保弹窗根对象激活
if (!邀请窗口.activeSelf)
{
邀请窗口.SetActive(true);
Debug.Log("弹窗根对象已激活");
}
// 确保整个层级处于激活状态
if (!gameObject.activeInHierarchy)
{
Debug.LogWarning("弹窗对象在非激活的父层级中,尝试修复...");
// 递归激活所有父对象
Transform current = transform;
while (current != null && !current.gameObject.activeSelf)
{
current.gameObject.SetActive(true);
current = current.parent;
}
}
}
// 安全启动倒计时协程
private void 启动倒计时协程()
{
// 先停止可能存在的旧协程
if (倒计时协程 != null)
{
StopCoroutine(倒计时协程);
倒计时协程 = null;
}
// 检查是否可以启动协程
if (isActiveAndEnabled && gameObject.activeInHierarchy)
{
倒计时协程 = StartCoroutine(倒计时逻辑());
Debug.Log("倒计时协程已启动");
}
else
{
Debug.LogError("无法启动协程:对象或脚本未激活!");
// 紧急处理:强制激活后重试
gameObject.SetActive(true);
邀请窗口.SetActive(true);
倒计时协程 = StartCoroutine(倒计时逻辑());
}
}
// 倒计时逻辑
private IEnumerator 倒计时逻辑()
{
float 剩余时间 = 超时时间;
while (剩余时间 > 0 && 正在显示)
{
剩余时间 -= Time.deltaTime;
if (倒计时文本 != null)
倒计时文本.text = $"({Mathf.Ceil(剩余时间)}s)";
yield return null;
}
// 超时自动处理
if (正在显示)
{
Debug.Log("弹窗超时,自动关闭");
隐藏弹窗(() => 拒绝回调?.Invoke());
}
}
// 隐藏弹窗
public void 隐藏弹窗(Action 完成回调 = null)
{
if (!正在显示) return;
// 停止倒计时
if (倒计时协程 != null)
{
StopCoroutine(倒计时协程);
倒计时协程 = null;
}
// 播放淡出动画
弹窗画布组.DOFade(0, 动画时长).SetEase(Ease.InQuad)
.OnComplete(() =>
{
// 关键:只隐藏视觉效果,不禁用对象
弹窗画布组.interactable = false;
弹窗画布组.blocksRaycasts = false;
正在显示 = false;
// 执行回调
完成回调?.Invoke();
// 显示下一个弹窗(如果有)
if (弹窗队列.Count > 0)
{
弹窗数据 下一个弹窗 = 弹窗队列.Dequeue();
内部显示弹窗(下一个弹窗.标题, 下一个弹窗.内容,
下一个弹窗.接受回调, 下一个弹窗.拒绝回调);
}
Debug.Log("弹窗已隐藏");
});
}
// 接受按钮点击
private void On接受按钮点击()
{
Debug.Log("接受按钮被点击");
隐藏弹窗(() =>
{
接受回调?.Invoke();
if (聊天框 != null)
{
聊天框.SetActive(true);
Debug.Log("聊天框已显示");
}
});
}
// 拒绝按钮点击
private void On拒绝按钮点击()
{
Debug.Log("拒绝按钮被点击");
隐藏弹窗(() => 拒绝回调?.Invoke());
}
// 清理资源
private void OnDestroy()
{
DOTween.KillAll();
弹窗队列.Clear();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class 地块生成系统 : MonoBehaviour
{
public 地块数据库 数据库;
private HashSet<(int X, int Y)> 已生成坐标 = new HashSet<(int X, int Y)>();
private Coroutine 生成协程 = null;
private 地块Data 上一个地块 = null;
public void 进入外出模式()
{
if (生成协程 != null)
{
StopCoroutine(生成协程);
生成协程 = null;
}
生成协程 = StartCoroutine(生成单个地块());
}
private IEnumerator 生成单个地块()
{
// 初始地块
if (数据库.地块列表.Count == 0)
{
上一个地块 = new 地块Data
{
序号 = 1,
X = 0,
Y = 0,
生态类型 = 获取随机生态类型()
};
yield return StartCoroutine(请求AI生成内容(上一个地块));
}
else
{
上一个地块 = 数据库.地块列表[数据库.地块列表.Count - 1];
// 获取相邻坐标
var 新坐标 = 获取随机相邻坐标(上一个地块.X, 上一个地块.Y);
if (已生成坐标.Contains((新坐标.x, 新坐标.y)))
{
Debug.Log("无法生成新地块,所有相邻坐标已被生成");
yield break;
}
var 新地块 = new 地块Data
{
序号 = 上一个地块.序号 + 1,
X = 新坐标.x,
Y = 新坐标.y,
生态类型 = 获取随机生态类型()
};
yield return StartCoroutine(请求AI生成内容(新地块));
数据库.添加地块(新地块);
已生成坐标.Add((新地块.X, 新地块.Y));
}
生成协程 = null;
}
private IEnumerator 请求AI生成内容(地块Data 地块)
{
string 提示词 = $@"你是一个生态科考AI助手,现在要生成一个新的地块信息。
该地块序号为 {地块.序号},坐标为 ({地块.X}, {地块.Y}),生态类型为:{地块.生态类型}。
请根据以下要求生成内容:
- 描述:简洁的环境描述(1~2句话)
- 生物列表:至少3种生物(可以是动物、植物、菌类)
返回格式为 JSON,示例格式如下:
{{
""描述"": """",
""生物列表"": [""狼"", ""兔子"", ""蘑菇""]
}}";
AIManager.Instance.发送消息给AI(提示词);
while (AIManager.Instance.当前回复 == null)
{
yield return null;
}
string AI回复 = AIManager.Instance.当前回复;
Debug.Log($"✅ AI返回地块信息:\n{AI回复}");
try
{
var 数据 = JsonUtility.FromJson<地块Data>(AI回复);
地块.描述 = 数据.描述;
地块.生物列表 = 数据.生物列表;
}
catch (System.Exception ex)
{
Debug.LogError("解析AI回复失败: " + ex.Message);
}
AIManager.Instance.当前回复 = null;
// 请求开场白(临时使用)
string 开场白提示词 = $@"你是一个生态科考AI助手,现在需要为以下地块生成一段开场白。
地块信息:
- 序号:{地块.序号}
- 坐标:({地块.X}, {地块.Y})
- 生态类型:{地块.生态类型}
- 描述:{地块.描述}
- 生物列表:{string.Join(", ", 地块.生物列表)}
请生成一段开场白,用于用户接受聊天邀请后显示在聊天框中。
";
AIManager.Instance.发送消息给AI(开场白提示词);
while (AIManager.Instance.当前回复 == null)
{
yield return null;
}
string 开场白 = AIManager.Instance.当前回复;
AIManager.Instance.当前回复 = null;
// 创建临时数据,包含地块信息 + 开场白
var 临时数据 = new 地块临时信息
{
地块 = 地块,
开场白 = 开场白
};
发出聊天邀请(临时数据);
}
private (int x, int y) 获取随机相邻坐标(int x, int y)
{
var 候选 = new List<(int x, int y)>
{
(x + 1, y),
(x - 1, y),
(x, y + 1),
(x, y - 1)
};
// 随机打乱
for (int i = 0; i < 候选.Count; i++)
{
int j = Random.Range(0, 候选.Count);
var temp = 候选[i];
候选[i] = 候选[j];
候选[j] = temp;
}
foreach (var coord in 候选)
{
if (!已生成坐标.Contains(coord))
{
return coord;
}
}
// 如果所有方向都已生成,返回原坐标(表示无法生成)
return (x, y);
}
private void 发出聊天邀请(地块临时信息 临时数据)
{
聊天邀请管理器.Instance.显示邀请(临时数据.开场白, () =>
{
聊天系统.Instance.打开聊天窗口(临时数据.地块);
});
}
private string 获取随机生态类型()
{
string[] 类型 = { "森林", "草原", "沙漠", "湿地", "雪山", "沼泽", "热带雨林" };
return 类型[Random.Range(0, 类型.Length)];
}
}
帮我修改,并完整输出代码