SoundGameController.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. using System.Collections;
  2. using UnityEngine;
  3. using UnityEngine.InputSystem;
  4. /* * WalkDogs 场景挂载在2D Player上
  5. * SoundGameController.cs
  6. * Author: 2023-10-12
  7. * Description: 音乐游戏控制器
  8. * 1. 读取音轨数据
  9. * 2. 创建音符
  10. * 3. 检测音符点击
  11. */
  12. public class SoundGameController : MonoBehaviour
  13. {
  14. SoundTrackManager soundTrackManager = new();
  15. GameObject notePrefabSwipe, notePrefabTap;
  16. float gameStartTime;
  17. float GameEndTime;
  18. // 游戏得分统计
  19. int score, combo, maxCombo, NGCombo, perfectCount, goodCount, poorCount, missCount, totalCount;
  20. // TapOrSwipe 触控检测
  21. Vector2 pressDownPosition = new Vector2(0, 0);
  22. Vector2 pressUpPosition = new Vector2(0, 0);
  23. float pressDownTime = 0f;
  24. float pressUpTime = 0f;
  25. // Start is called once before the first execution of Update after the MonoBehaviour is created
  26. void Start()
  27. {
  28. TextAsset soundTrackJson = Resources.Load<TextAsset>("WalkDogs/SoundTrack/track_1");
  29. if (soundTrackJson != null)
  30. {
  31. string soundTrackString = soundTrackJson.text;
  32. soundTrackManager.ImportFromJson(soundTrackString);
  33. soundTrackManager.GenRandomActions();
  34. // soundTrackManager.speed = 4;
  35. }
  36. notePrefabSwipe = Resources.Load<GameObject>("WalkDogs/Note/NoteSwipe");
  37. notePrefabTap = Resources.Load<GameObject>("WalkDogs/Note/NoteTap");
  38. StartCoroutine(PlaySoundGame());
  39. }
  40. // Update is called once per frame
  41. void Update()
  42. {
  43. }
  44. IEnumerator PlaySoundGame()
  45. {
  46. // 加载音乐
  47. AudioClip audioClip = Resources.Load<AudioClip>(soundTrackManager.soundTrack);
  48. AudioSource audioSource = gameObject.AddComponent<AudioSource>();
  49. audioSource.clip = audioClip;
  50. yield return new WaitForSeconds(2f); // 等待音频加载完成
  51. audioSource.Play();
  52. gameStartTime = Time.time;
  53. GameEndTime = gameStartTime + soundTrackManager.length;
  54. while (Time.time < GameEndTime)
  55. {
  56. // 用于提前n秒创建音符
  57. foreach (var note in soundTrackManager.soundAction)
  58. {
  59. if (!note.isActive && Time.time >= (gameStartTime + note.time - GameData.noteStartLeadTime))
  60. {
  61. note.isActive = true;
  62. CreateNote(note.time, note.action, soundTrackManager.soundAction.IndexOf(note).ToString());
  63. continue;
  64. }
  65. }
  66. // 检测音符是否超时未被点击
  67. foreach (var note in soundTrackManager.soundAction)
  68. {
  69. float TapTimeDeadline = note.time + gameStartTime + 0.35f; // 计算点击事件超时
  70. if (note.isActive && !note.isTapped && Time.time > TapTimeDeadline)
  71. {
  72. note.isTapped = true;
  73. missCount++;
  74. NGCombo++;
  75. Debug.Log("Miss!" + Time.time + " Tap time deadline: " + TapTimeDeadline + " total miss:" + missCount);
  76. Debug.Log("index of note:" + soundTrackManager.soundAction.IndexOf(note));
  77. break;
  78. }
  79. }
  80. // 检测游戏是否结束并统计分数
  81. totalCount = perfectCount + goodCount + poorCount + missCount;
  82. if (totalCount == soundTrackManager.soundAction.Count)
  83. {
  84. GameEndTime = Time.time;
  85. Debug.Log("Game Over!");
  86. Debug.Log($"Perfect: {perfectCount}, Good: {goodCount}, Poor: {poorCount}, Miss: {missCount}");
  87. Debug.Log($"Total Count: {totalCount}");
  88. Debug.Log($"Score: {score}");
  89. Debug.Log($"Combo: {combo}");
  90. Debug.Log($"Max Combo: {maxCombo}");
  91. // 结束游戏
  92. break; // 跳出 while 循环
  93. }
  94. // 当NGCombo大于5时,播放音效提示
  95. if (NGCombo > 5)
  96. {
  97. SoundGameEffectController.Instance.PlaySoundEffect(0);
  98. NGCombo = 0;
  99. }
  100. yield return new WaitForSeconds(0.01f);
  101. }
  102. yield return null;
  103. }
  104. private void CreateNote(float hitTime, string action, string noteName)
  105. {
  106. GameObject note;
  107. if (action == "tap")
  108. {
  109. note = Instantiate(notePrefabTap);
  110. Debug.Log("Create Tap Note: " + noteName);
  111. Debug.Log("Hit Time: " + hitTime + ", Action: " + action);
  112. Debug.Log("Create Time: " + Time.time);
  113. }
  114. else
  115. {
  116. note = Instantiate(notePrefabSwipe);
  117. Debug.Log("Create Tap Note: " + noteName);
  118. Debug.Log("Hit Time: " + hitTime + ", Action: " + action);
  119. Debug.Log("Create Time: " + Time.time);
  120. }
  121. note.gameObject.name = "note_" + noteName;
  122. NoteController noteController = note.GetComponent<NoteController>();
  123. noteController.speed = soundTrackManager.speed;
  124. noteController.hitTime = hitTime;
  125. noteController.action = action;
  126. }
  127. // 触控检测,检测通过按下和和抬起的触控点位置计算点击或者上下左右滑动
  128. public void TapOrSwipe(InputAction.CallbackContext context)
  129. {
  130. if (context.phase == InputActionPhase.Started)
  131. {
  132. pressDownPosition = Pointer.current.position.ReadValue();
  133. pressDownTime = Time.time;
  134. return;
  135. }
  136. if (context.phase == InputActionPhase.Canceled)
  137. {
  138. pressUpPosition = Pointer.current.position.ReadValue();
  139. pressUpTime = Time.time;
  140. float deltaX = pressUpPosition.x - pressDownPosition.x;
  141. float deltaY = pressUpPosition.y - pressDownPosition.y;
  142. float distance = Mathf.Sqrt(deltaX * deltaX + deltaY * deltaY);
  143. // Debug.Log($"deltaX: {deltaX}, deltaY: {deltaY}, distance: {distance}");
  144. float timeDelta = pressUpTime - pressDownTime;
  145. string action;
  146. if (timeDelta > 0.2f)
  147. {
  148. return;
  149. }
  150. else
  151. {
  152. if (distance < 20f)
  153. {
  154. action = "tap";
  155. }
  156. else
  157. {
  158. if (Mathf.Abs(deltaX) > Mathf.Abs(deltaY))
  159. {
  160. if (deltaX > 0)
  161. {
  162. action = "right";
  163. }
  164. else
  165. {
  166. action = "left";
  167. }
  168. }
  169. else
  170. {
  171. if (deltaY > 0)
  172. {
  173. action = "up";
  174. }
  175. else
  176. {
  177. action = "down";
  178. }
  179. }
  180. }
  181. }
  182. TouchResponse touchResponse = new TouchResponse(action, pressUpTime);
  183. // Debug.Log($"TouchResponse: {touchResponse.action}, {touchResponse.time}");
  184. // 检测点击的音符
  185. ScoreCheck(touchResponse);
  186. }
  187. return;
  188. }
  189. private void ScoreCheck(TouchResponse touchResponse)
  190. {
  191. if (touchResponse != null)
  192. {
  193. foreach (var note in soundTrackManager.soundAction)
  194. {
  195. float TapTimeDelta = Mathf.Abs(touchResponse.time - (note.time + gameStartTime)); // 计算点击时间和音符时间的差值
  196. if (note.isActive && !note.isTapped && TapTimeDelta < 0.3f)
  197. {
  198. note.isTapped = true;
  199. if (touchResponse.action == note.action)
  200. {
  201. if (TapTimeDelta < 0.1f)
  202. {
  203. score += 100;
  204. combo++;
  205. if (combo > maxCombo)
  206. {
  207. maxCombo = combo;
  208. }
  209. perfectCount++;
  210. NGCombo = 0;
  211. Debug.Log("Perfect!" + Time.time + " total perfect:" + perfectCount);
  212. string noteName = soundTrackManager.soundAction.IndexOf(note).ToString();
  213. DestroyNote(noteName);
  214. }
  215. else if (TapTimeDelta < 0.2f)
  216. {
  217. score += 30;
  218. combo = 0;
  219. goodCount++;
  220. NGCombo = 0;
  221. Debug.Log("Good!" + Time.time + " total good:" + goodCount);
  222. string noteName = soundTrackManager.soundAction.IndexOf(note).ToString();
  223. DestroyNote(noteName);
  224. }
  225. else
  226. {
  227. score += 10;
  228. combo = 0;
  229. poorCount++;
  230. NGCombo++;
  231. Debug.Log("Poor!" + Time.time + " total poor:" + poorCount);
  232. }
  233. }
  234. else
  235. {
  236. combo = 0;
  237. missCount++;
  238. NGCombo++;
  239. Debug.Log("Wrong!" + Time.time + " total miss:" + missCount);
  240. }
  241. Debug.Log("index of note:" + soundTrackManager.soundAction.IndexOf(note));
  242. break;
  243. }
  244. }
  245. }
  246. }
  247. private void DestroyNote(string noteName)
  248. {
  249. GameObject note = GameObject.Find("note_" + noteName);
  250. if (note != null)
  251. {
  252. Destroy(note);
  253. }
  254. else
  255. {
  256. Debug.Log("Note not found: " + noteName);
  257. }
  258. }
  259. }
  260. public class TouchResponse
  261. {
  262. public string action = ""; // 触控动作 up, down, left, right, tap, none(表示无效)
  263. public float time;
  264. public TouchResponse(string action, float time)
  265. {
  266. this.action = action;
  267. this.time = time;
  268. }
  269. }