RankManager.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. using UnityEngine;
  2. using UnityEngine.UI;
  3. using System.Collections.Generic;
  4. using System.Collections;
  5. using UnityEngine.Networking;
  6. using Newtonsoft.Json.Linq;
  7. using System;
  8. namespace LocalRank
  9. {
  10. [RequireComponent(typeof(RankUpAnimator))]
  11. public class RankManager : MonoBehaviour
  12. {
  13. public Transform Panel; //是否显示
  14. public Transform content; // Content节点
  15. public GameObject itemPrefab; // 排行榜Item预制体
  16. public InputField inputField;
  17. private RankUpAnimator rankUpAnimator; // RankUpAnimator脚本引用
  18. private List<RankItemData> rankDataList = new List<RankItemData>();
  19. /// <summary>
  20. /// 当前的排名分数
  21. /// </summary>
  22. public static int CurrentScore = 0;
  23. /// <summary>
  24. /// 维护一个全局的userId
  25. /// </summary>
  26. public static int userId { get; private set; }
  27. /// <summary>
  28. /// 是否创建userId
  29. /// </summary>
  30. private static bool userIdCreated = false;
  31. public static bool HasUserId => userIdCreated;
  32. /// <summary>
  33. /// 是否在滚动
  34. /// </summary>
  35. private bool isRuning = false;
  36. //退出
  37. public Action OnBackClicked;
  38. // 你可以在这里定义游戏 ID 或者动态获取
  39. private int gameId = 1;
  40. /// <summary>
  41. /// 创建一个排行榜
  42. /// </summary>
  43. /// <param name="gameType"></param>
  44. public static void CreateRankView(int gameType,Action callback) {
  45. Debug.Log("当前上传的本地排行榜ID:"+ gameType);
  46. //b端时候处理一个结束排行榜
  47. GameObject rankObj = ViewManager2.getGameObjectAndShowView(ViewManager2.Path_GameRankView);
  48. RankManager gameRankView = rankObj.GetComponent<RankManager>();
  49. gameRankView.OnBackClicked += ()=> {
  50. ViewManager2.HideView(ViewManager2.Path_GameRankView);
  51. callback?.Invoke();
  52. };
  53. gameRankView.CreateRankGameInfo(gameType);
  54. gameRankView.UploadCurrentScoreToNewId();
  55. }
  56. private void Awake()
  57. {
  58. rankUpAnimator = GetComponent<RankUpAnimator>();
  59. if (rankUpAnimator == null)
  60. {
  61. Debug.LogError("RankUpAnimator 组件未找到!");
  62. }
  63. if (inputField != null)
  64. {
  65. inputField.onEndEdit.AddListener(InputScore);
  66. }
  67. }
  68. private void Start()
  69. {
  70. rankUpAnimator.onAnimationStart = () =>
  71. {
  72. isRuning = true;
  73. HideAllRankIcons(); // 滚动前隐藏图标和名次数字
  74. };
  75. rankUpAnimator.onAnimationComplete = () =>
  76. {
  77. isRuning = false;
  78. RefreshRankIcons(); // 滚动后恢复图标或数字
  79. };
  80. //CreateRankGameInfo();
  81. //CreateUserId();
  82. }
  83. /// <summary>
  84. /// 设置分数。用于后续上传排行榜显示
  85. /// </summary>
  86. /// <param name="score"></param>
  87. public static void SetCurrentScore(int score = 0) {
  88. if (score > 0) {
  89. CurrentScore = score;
  90. }
  91. }
  92. /// <summary>
  93. /// 创建本地排行榜,进入场景时候直接创建。比如游戏场景
  94. /// 不同的游戏ID对应不同的排行榜
  95. /// </summary>
  96. public void CreateRankGameInfo(int CurrentGameId = 1)
  97. {
  98. gameId = CurrentGameId;
  99. // 判断是否已创建表
  100. if (RankService.IsTableExists(gameId))
  101. {
  102. Debug.Log($"rank_game_{gameId} 表已存在!");
  103. }
  104. else
  105. {
  106. Debug.Log($"rank_game_{gameId} 表不存在,正在创建...");
  107. RankService.CreateTableIfNotExists(gameId);
  108. }
  109. }
  110. /// <summary>
  111. /// 如果需要维护一个常用id的话
  112. /// 调用这里创建userId,每个userId都是单独记录排行榜数据。
  113. /// </summary>
  114. public void CreateUserId()
  115. {
  116. //获取一个时间戳值
  117. userId = RankService.GenerateLocalUserId();
  118. //记录一个标识
  119. userIdCreated = true;
  120. }
  121. /// <summary>
  122. /// 直接上传分数到当前用户信息
  123. /// </summary>
  124. public void UploadCurrentScoreToUserId()
  125. {
  126. if (!HasUserId)
  127. {
  128. Debug.LogError("无效的本地用户id!");
  129. return;
  130. }
  131. if (CurrentScore != 0)
  132. {
  133. if (isRuning)
  134. {
  135. Debug.LogError("操作频繁!等排行榜停止在操作!");
  136. return;
  137. }
  138. RankService.UploadScore(gameId, userId, "Player_" + userId, "", CurrentScore);
  139. Debug.Log($"UploadScoreToUserId rank_game_{gameId} 上传 Id:{userId}分数:{CurrentScore}!");
  140. GetLocalRankList();
  141. CurrentScore = 0;
  142. }
  143. }
  144. /// <summary>
  145. /// 上传当前操作分数到新用户
  146. /// </summary>
  147. public void UploadCurrentScoreToNewId() {
  148. if (CurrentScore != 0)
  149. {
  150. if (isRuning)
  151. {
  152. Debug.LogError("操作频繁!等排行榜停止在操作!");
  153. return;
  154. }
  155. Panel.gameObject.SetActive(true);
  156. UploadNewIdScore(CurrentScore);
  157. CurrentScore = 0;
  158. }
  159. else
  160. {
  161. //分数是0直接进行下一步
  162. OnBackClicked?.Invoke();
  163. }
  164. }
  165. /// <summary>
  166. /// 直接上传一个新用户id分数
  167. /// </summary>
  168. private void UploadNewIdScore(int score = 0)
  169. {
  170. int newUserId = RankService.GenerateLocalUserId();
  171. RankService.UploadScore(gameId, newUserId, "Player_" + newUserId, "", score);
  172. Debug.Log($"UploadNewIdScore rank_game_{gameId} 上传 新Id:{newUserId}分数:{score}!");
  173. //更新当前用户信息
  174. userId = newUserId;
  175. userIdCreated = true;
  176. GetLocalRankList();
  177. }
  178. #region 测试数据操作
  179. /// <summary>
  180. /// 通过Input End 上传分数
  181. /// </summary>
  182. /// <param name="score"></param>
  183. void InputScore(string score)
  184. {
  185. if (!HasUserId)
  186. {
  187. Debug.LogError("无效的本地用户id!");
  188. return;
  189. }
  190. int parsedScore;
  191. if (!int.TryParse(score, out parsedScore))
  192. {
  193. Debug.LogError("输入的分数无效!");
  194. return;
  195. }
  196. if (isRuning)
  197. {
  198. Debug.LogError("操作频繁!等排行榜停止在操作!");
  199. return;
  200. }
  201. RankService.UploadScore(gameId, userId, "Player_" + userId, "", parsedScore);
  202. Debug.Log($"InputScore rank_game_{gameId} 上传 Id:{userId}分数:{parsedScore}");
  203. GetLocalRankList();
  204. }
  205. /// <summary>
  206. /// 上传测试分数
  207. /// </summary>
  208. /// <param name="score"></param>
  209. public void UploadTestRandomScoreToUserId()
  210. {
  211. if (!HasUserId)
  212. {
  213. Debug.LogError("无效的本地用户id!");
  214. return;
  215. }
  216. if (isRuning)
  217. {
  218. Debug.LogError("操作频繁!等排行榜停止在操作!");
  219. return;
  220. }
  221. int seed = 0;
  222. var random = seed == 0 ? new System.Random() : new System.Random(seed);
  223. int _score = random.Next(0, 1000); // 随机的分数 0~999 分之间
  224. RankService.UploadScore(gameId, userId, "Player_" + userId, "", _score);
  225. Debug.Log($"UploadTestRandomScoreToUserId rank_game_{gameId} 上传 Id:{userId}分数:{_score}!");
  226. GetLocalRankList();
  227. }
  228. /// <summary>
  229. /// 上传随机分数到新用户
  230. /// </summary>
  231. public void UploadTestRandomScoreToNewId()
  232. {
  233. if (isRuning)
  234. {
  235. Debug.LogError("操作频繁!等排行榜停止在操作!");
  236. return;
  237. }
  238. int seed = 0;
  239. var random = seed == 0 ? new System.Random() : new System.Random(seed);
  240. int _score = random.Next(1, 1000); // 随机的分数是 1~999 分之间
  241. UploadNewIdScore(_score);
  242. }
  243. /// <summary>
  244. /// 测试数据调用
  245. /// </summary>
  246. public void TestData()
  247. {
  248. InsertTestData();
  249. }
  250. /// <summary>
  251. /// 按钮调用,重新填充排行榜数据
  252. /// </summary>
  253. // public void AnimatorRunButton()
  254. // {
  255. // PopulateRankList(true); // 重新填充排行榜数据
  256. // }
  257. /// <summary>
  258. /// 插入测试数据
  259. /// </summary>
  260. /// <param name="seed"></param>
  261. private void InsertTestData(int seed = 0)
  262. {
  263. var random = seed == 0 ? new System.Random() : new System.Random(seed);
  264. for (int i = 1; i <= 20; i++)
  265. {
  266. int userId = 1000 + i;
  267. string nickname = $"玩家{i:D2}";
  268. string avatarUrl = ""; // 可以填测试图片链接或留空
  269. int score = random.Next(0, 10000); // 0~9999 分之间
  270. RankService.UploadScore(gameId, userId, nickname, avatarUrl, score);
  271. }
  272. Debug.Log($"已为 gameId={gameId} 插入 20 条测试数据");
  273. }
  274. #endregion
  275. /// <summary>
  276. /// 获取本地排行榜
  277. /// </summary>
  278. void GetLocalRankList()
  279. {
  280. if (!HasUserId)
  281. {
  282. Debug.LogError("先创建用户id再获取排行榜!");
  283. return;
  284. }
  285. rankDataList.Clear();
  286. RankItemData selfItem = null;
  287. const int MaxRankDisplayCount = 9; // 展示排行榜上限,最后一位预留给自己
  288. int maxRank = 0;//记录最大的max
  289. int index = 0;//当前下标
  290. var (ranks, selfRank, total) = RankService.GetRankList(gameId: gameId, userId: userId);
  291. foreach (var r in ranks)
  292. {
  293. // Debug.Log($"[{r.Rank}] {r.Nickname} - {r.Score} {(r.IsSelf ? "(你)" : "")}");
  294. var data = new RankItemData
  295. {
  296. Rank = r.Rank,
  297. UserName = r.Nickname,
  298. Score = r.Score,
  299. AvatarUrl = r.AvatarUrl,
  300. IsSelf = r.IsSelf,
  301. RankIndex = index++
  302. };
  303. // Debug.Log($"成功解析data {data} 排行榜数据");
  304. if (data.IsSelf)
  305. selfItem = data;
  306. else
  307. rankDataList.Add(data);
  308. maxRank = Mathf.Max(maxRank, r.Rank);//记录最大值
  309. }
  310. //Debug.Log($"成功解析 {rankDataList.Count} 条排行榜数据");
  311. // 如果总数量不足 MaxRankDisplayCount,补充假数据
  312. int currentCount = rankDataList.Count;
  313. int otherIndex = 0;
  314. for (int i = currentCount; i < MaxRankDisplayCount; i++)
  315. {
  316. ++otherIndex;
  317. var fakeData = new RankItemData
  318. {
  319. Rank = maxRank + otherIndex,
  320. UserName = $"游客玩家 {i + 1}",
  321. Score = 0,
  322. AvatarUrl = "", // 可替换为默认头像路径
  323. IsSelf = false,
  324. RankIndex = index++
  325. };
  326. rankDataList.Add(fakeData);
  327. }
  328. // 最后添加自己(先放在最后一位)
  329. if (selfItem != null)
  330. {
  331. rankDataList.Add(selfItem);
  332. }
  333. // Debug.Log($"最终排行榜数据条数: {rankDataList.Count}");
  334. PopulateRankList(true);
  335. }
  336. /// <summary>
  337. /// 获取远程排行榜数据
  338. /// 暂时不使用
  339. /// </summary>
  340. /// <returns></returns>
  341. private IEnumerator FetchRankDataFromServer()
  342. {
  343. string url = "http://192.168.0.108:11432/SmartBowBusinessServer/SmartBowSDK/UploadScoreAndGetSeasonCenterRankList" +
  344. "?token=3421-1746758375311-5786b88c34343d142bea1dd31a1cb315" +
  345. "&rankArea=2&rankGameType=1001&score=9937" +
  346. "&countryCode=中国&stateCode=广东省&cityCode=清远市";
  347. UnityWebRequest request = UnityWebRequest.Post(url, "");
  348. yield return request.SendWebRequest();
  349. if (request.result != UnityWebRequest.Result.Success)
  350. {
  351. Debug.LogError("Request Failed: " + request.error);
  352. yield break;
  353. }
  354. string json = request.downloadHandler.text;
  355. ParseRankJson(json);
  356. }
  357. /// <summary>
  358. /// 解析远程排行榜数据
  359. /// </summary>
  360. /// <param name="json"></param>
  361. private void ParseRankJson(string json)
  362. {
  363. rankDataList.Clear();
  364. JObject root = JObject.Parse(json);
  365. JArray rankArray = (JArray)root["data"][0];
  366. RankItemData selfItem = null;
  367. int index = 0;
  368. foreach (JObject item in rankArray)
  369. {
  370. var data = new RankItemData
  371. {
  372. Rank = item["rankNum"] != null ? (int)item["rankNum"] : 0,
  373. UserName = item["nickname"]?.ToString() ?? "Unknown",
  374. Score = item["score"] != null ? (int)item["score"] : 0,
  375. AvatarUrl = item["avatarUrl"]?.ToString() ?? "",
  376. IsSelf = item["isSelf"]?.ToObject<bool>() ?? false,
  377. RankIndex = index++
  378. };
  379. Debug.Log($"成功解析data {data} 排行榜数据");
  380. if (data.IsSelf)
  381. selfItem = data;
  382. else
  383. rankDataList.Add(data);
  384. }
  385. // 最后添加自己(先放在最后一位)
  386. if (selfItem != null)
  387. {
  388. rankDataList.Add(selfItem);
  389. }
  390. Debug.Log($"成功解析 {rankDataList.Count} 条排行榜数据");
  391. PopulateRankList();
  392. }
  393. /// <summary>
  394. /// 开始跑循环分数时候调用
  395. /// </summary>
  396. public void HideAllRankIcons()
  397. {
  398. for (int i = 0; i < content.childCount; i++)
  399. {
  400. var item = content.GetChild(i);
  401. var itemUI = item.GetComponent<RankItemUI>();
  402. if (itemUI != null)
  403. {
  404. //itemUI.HideIconAndText();
  405. itemUI.StartRunUI();
  406. }
  407. }
  408. }
  409. /// <summary>
  410. /// 结束循环分数时候调用
  411. /// </summary>
  412. public void RefreshRankIcons()
  413. {
  414. for (int i = 0; i < content.childCount; i++)
  415. {
  416. var item = content.GetChild(i);
  417. var itemUI = item.GetComponent<RankItemUI>();
  418. if (itemUI != null)
  419. {
  420. itemUI.StopRunUI();
  421. }
  422. }
  423. }
  424. /// <summary>
  425. /// 填充排行榜列表,生成排行榜预制item
  426. /// </summary>
  427. /// <param name="isStartup"></param>
  428. private void PopulateRankList(bool isStartup = false)
  429. {
  430. foreach (Transform child in content)
  431. Destroy(child.gameObject);
  432. for (int i = 0; i < rankDataList.Count; i++)
  433. {
  434. var data = rankDataList[i];
  435. GameObject itemObj = Instantiate(itemPrefab, content);
  436. itemObj.name = $"RankItem_{data.Rank}";
  437. RankItemUI itemUI = itemObj.GetComponent<RankItemUI>();
  438. if (itemUI != null)
  439. {
  440. itemUI.SetData(data);
  441. if (!string.IsNullOrEmpty(data.AvatarUrl))
  442. StartCoroutine(LoadAvatarImage(data.AvatarUrl, itemUI.avatarImage));
  443. }
  444. else
  445. {
  446. Debug.LogWarning("预制体上缺少 RankItemUI 脚本");
  447. }
  448. }
  449. if (isStartup)
  450. StartCoroutine(DelayedStartAnimation());
  451. }
  452. /// <summary>
  453. /// 加载头像图片
  454. /// </summary>
  455. /// <param name="url"></param>
  456. /// <param name="targetImage"></param>
  457. /// <returns></returns>
  458. private IEnumerator LoadAvatarImage(string url, Image targetImage)
  459. {
  460. UnityWebRequest request = UnityWebRequestTexture.GetTexture(url);
  461. yield return request.SendWebRequest();
  462. if (request.result != UnityWebRequest.Result.Success)
  463. {
  464. Debug.LogWarning("头像加载失败: " + request.error);
  465. yield break;
  466. }
  467. Texture2D texture = DownloadHandlerTexture.GetContent(request);
  468. Sprite avatarSprite = Sprite.Create(texture,
  469. new Rect(0, 0, texture.width, texture.height),
  470. new Vector2(0.5f, 0.5f));
  471. targetImage.sprite = avatarSprite;
  472. }
  473. /// <summary>
  474. /// 延迟开始动画
  475. /// </summary>
  476. /// <returns></returns>
  477. IEnumerator DelayedStartAnimation()
  478. {
  479. yield return null;
  480. // TestRankUpFromTo(10, 1);
  481. //查找“自己”的索引和目标真实排名索引
  482. int startIndex = rankDataList.Count;
  483. RankItemData selfData = rankDataList.Find(d => d.IsSelf);
  484. int targetIndex = selfData.RankIndex + 1; // 目标名次是自己在列表中的索引 + 1
  485. // for (int i = 0; i < rankDataList.Count; i++)
  486. // {
  487. // var data = rankDataList[i];
  488. // Debug.Log($"[{data.Rank}] {data.UserName} - {data.Score} {(data.IsSelf ? "(你)" : "")} ,RankIndex: {data.RankIndex}");
  489. // }
  490. // Debug.Log($"startIndex:{startIndex} targetIndex:{targetIndex}");
  491. if (startIndex != targetIndex)
  492. {
  493. RankUpFromTo(startIndex, targetIndex);
  494. }
  495. }
  496. /// <summary>
  497. /// 测试冲榜动画:从 startIndex 冲到 targetRankIndex
  498. /// </summary>
  499. void RankUpFromTo(int startIndex, int targetRankIndex)
  500. {
  501. if (startIndex <= 0 || startIndex > content.childCount)
  502. {
  503. Debug.LogError("无效的起始排名!");
  504. return;
  505. }
  506. if (targetRankIndex <= 0 || targetRankIndex > content.childCount)
  507. {
  508. Debug.LogError("无效的目标排名!");
  509. return;
  510. }
  511. // 获取起始 item
  512. Transform startItem = content.GetChild(startIndex - 1);
  513. // 设置 animator 的目标元素和目标名次
  514. rankUpAnimator.targetItem = startItem.GetComponent<RectTransform>();
  515. rankUpAnimator.targetRankIndex = targetRankIndex;
  516. // 调用冲榜动画
  517. rankUpAnimator.StartRankUpAnimation(startIndex, targetRankIndex);
  518. }
  519. public void OnClick_Back()
  520. {
  521. if (isRuning) return;
  522. //AudioMgr.ins.PlayBtn();
  523. OnBackClicked?.Invoke();
  524. }
  525. }
  526. }