SoundGameController.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using UnityEngine.InputSystem;
  6. /* * WalkDogs 场景挂载在2D Player上
  7. * SoundGameController.cs
  8. * Author: 2023-10-12
  9. * Description: 音乐游戏控制器
  10. * 1. 读取音轨数据
  11. * 2. 创建音符
  12. * 3. 检测音符点击
  13. */
  14. public class SoundGameController : MonoBehaviour
  15. {
  16. SoundTrackManager soundTrackManager = new();
  17. GameObject notePrefabSwipe, notePrefabTap;
  18. float gameStartTime;
  19. float GameEndTime;
  20. // 游戏得分统计
  21. int score, combo, maxCombo, NGCombo, perfectCount, goodCount, poorCount, missCount, totalCount;
  22. // 游戏数据
  23. string currentDogState = "walking"; // 是否有狗在跑
  24. // TapOrSwipe 触控检测
  25. Vector2 pressDownPosition = new Vector2(0, 0);
  26. Vector2 pressUpPosition = new Vector2(0, 0);
  27. float pressDownTime = 0f;
  28. float pressUpTime = 0f;
  29. // Start is called once before the first execution of Update after the MonoBehaviour is created
  30. void Start()
  31. {
  32. TextAsset soundTrackJson = Resources.Load<TextAsset>("WalkDogs/SoundTrack/track_1");
  33. if (soundTrackJson != null)
  34. {
  35. string soundTrackString = soundTrackJson.text;
  36. soundTrackManager.ImportFromJson(soundTrackString);
  37. soundTrackManager.GenRandomActions();
  38. // soundTrackManager.speed = 4;
  39. }
  40. notePrefabSwipe = Resources.Load<GameObject>("WalkDogs/Note/NoteSwipe");
  41. notePrefabTap = Resources.Load<GameObject>("WalkDogs/Note/NoteTap");
  42. StartCoroutine(PlaySoundGame());
  43. StartCoroutine(DogComponentInstaller());
  44. }
  45. // Update is called once per frame
  46. void Update()
  47. {
  48. DogStatusUpdate();
  49. }
  50. IEnumerator DogComponentInstaller()
  51. {
  52. yield return null;
  53. // 找到所有标签为 "dog" 的 GameObject
  54. GameObject[] dogObjects = GameObject.FindGameObjectsWithTag("dog");
  55. foreach (GameObject dog in dogObjects)
  56. {
  57. // 加载指定的Animator controller
  58. Animator animator = dog.GetComponent<Animator>();
  59. DogProperty dogProperty = new DogProperty();
  60. foreach (var dogProp in UserProperty.dogs)
  61. {
  62. if (dogProp.dog_name == dog.name)
  63. {
  64. dogProperty = dogProp;
  65. break;
  66. }
  67. }
  68. RuntimeAnimatorController animatorController = Resources.Load<RuntimeAnimatorController>("WalkDogs/Animation/shibaInu");
  69. if (dogProperty.breed == "shibaInu")
  70. {
  71. animatorController = Resources.Load<RuntimeAnimatorController>("WalkDogs/Animation/shibaInu");
  72. }
  73. animator.runtimeAnimatorController = animatorController;
  74. // 加载Rigidbody
  75. Rigidbody rigidbody = dog.AddComponent<Rigidbody>();
  76. //rigidbody.isKinematic = true;
  77. rigidbody.mass = 10;
  78. rigidbody.linearDamping = 10;
  79. rigidbody.angularDamping = 10;
  80. //rigidbody.freezeRotation = true;
  81. rigidbody.constraints = RigidbodyConstraints.FreezePositionY | RigidbodyConstraints.FreezeRotation;
  82. rigidbody.interpolation = RigidbodyInterpolation.Interpolate;
  83. rigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
  84. // 加载box collider
  85. BoxCollider boxCollider = dog.AddComponent<BoxCollider>();
  86. boxCollider.isTrigger = false;
  87. boxCollider.center = new Vector3(0, 0.25f, 0);
  88. boxCollider.size = new Vector3(0.2f, 0.5f, 0.6f);
  89. }
  90. }
  91. IEnumerator PlaySoundGame()
  92. {
  93. // 加载背景
  94. List<string> backgroundList = new List<string>
  95. {
  96. "WalkDogs/Background/bg_1",
  97. "WalkDogs/Background/bg_2",
  98. "WalkDogs/Background/bg_3",
  99. "WalkDogs/Background/bg_4",
  100. };
  101. var Background = GameObject.Find("Background");
  102. MeshRenderer backgroundMeshRenderer = Background.GetComponent<MeshRenderer>();
  103. float lastBackgroundTime;
  104. // 加载音乐
  105. AudioClip audioClip = Resources.Load<AudioClip>(soundTrackManager.soundTrack);
  106. AudioSource audioSource = gameObject.AddComponent<AudioSource>();
  107. audioSource.clip = audioClip;
  108. yield return new WaitForSeconds(2f); // 等待音频加载完成
  109. audioSource.Play();
  110. gameStartTime = Time.time;
  111. GameEndTime = gameStartTime + soundTrackManager.length;
  112. lastBackgroundTime = gameStartTime;
  113. while (Time.time < GameEndTime)
  114. {
  115. // 每隔15秒切换一次背景
  116. if (Time.time - lastBackgroundTime > 15f)
  117. {
  118. lastBackgroundTime = Time.time;
  119. int randomIndex = UnityEngine.Random.Range(0, backgroundList.Count);
  120. string backgroundPath = backgroundList[randomIndex];
  121. Material myMaterial = Resources.Load<Material>(backgroundPath);
  122. if (myMaterial != null)
  123. {
  124. backgroundMeshRenderer.material = myMaterial;
  125. // Debug.Log("Background changed to: " + backgroundPath);
  126. }
  127. else
  128. {
  129. Debug.LogWarning("Background material not found: " + backgroundPath);
  130. }
  131. }
  132. // 用于提前n秒创建音符
  133. foreach (var note in soundTrackManager.soundAction)
  134. {
  135. if (!note.isActive && Time.time >= (gameStartTime + note.time - GameData.noteStartLeadTime))
  136. {
  137. note.isActive = true;
  138. CreateNote(note.time, note.action, soundTrackManager.soundAction.IndexOf(note).ToString());
  139. continue;
  140. }
  141. }
  142. // 检测音符是否超时未被点击
  143. foreach (var note in soundTrackManager.soundAction)
  144. {
  145. float TapTimeDeadline = note.time + gameStartTime + 0.35f; // 计算点击事件超时
  146. if (note.isActive && !note.isTapped && Time.time > TapTimeDeadline)
  147. {
  148. note.isTapped = true;
  149. missCount++;
  150. NGCombo++;
  151. combo = 0; // 重置连击
  152. // Debug.Log("Miss!" + Time.time + " Tap time deadline: " + TapTimeDeadline + " total miss:" + missCount);
  153. // Debug.Log("index of note:" + soundTrackManager.soundAction.IndexOf(note));
  154. break;
  155. }
  156. }
  157. // 检测游戏是否结束并统计分数
  158. totalCount = perfectCount + goodCount + poorCount + missCount;
  159. if (totalCount == soundTrackManager.soundAction.Count)
  160. {
  161. GameEndTime = Time.time;
  162. Debug.Log("Game Over!");
  163. Debug.Log($"Perfect: {perfectCount}, Good: {goodCount}, Poor: {poorCount}, Miss: {missCount}");
  164. Debug.Log($"Total Count: {totalCount}");
  165. Debug.Log($"Score: {score}");
  166. Debug.Log($"Combo: {combo}");
  167. Debug.Log($"Max Combo: {maxCombo}");
  168. // 结束游戏
  169. break; // 跳出 while 循环
  170. }
  171. // 当NGCombo大于5时,播放音效提示
  172. if (NGCombo > 5)
  173. {
  174. SoundGameEffectController.Instance.PlaySoundEffect(0);
  175. NGCombo = 0;
  176. }
  177. yield return new WaitForSeconds(0.01f);
  178. }
  179. yield return null;
  180. }
  181. private void CreateNote(float hitTime, string action, string noteName)
  182. {
  183. GameObject note;
  184. if (action == "tap")
  185. {
  186. note = Instantiate(notePrefabTap);
  187. // Debug.Log("Create Tap Note: " + noteName);
  188. // Debug.Log("Hit Time: " + hitTime + ", Action: " + action);
  189. // Debug.Log("Create Time: " + Time.time);
  190. }
  191. else
  192. {
  193. note = Instantiate(notePrefabSwipe);
  194. // Debug.Log("Create Tap Note: " + noteName);
  195. // Debug.Log("Hit Time: " + hitTime + ", Action: " + action);
  196. // Debug.Log("Create Time: " + Time.time);
  197. }
  198. note.gameObject.name = "note_" + noteName;
  199. NoteController noteController = note.GetComponent<NoteController>();
  200. noteController.speed = soundTrackManager.speed;
  201. noteController.hitTime = hitTime;
  202. noteController.action = action;
  203. }
  204. // 触控检测,检测通过按下和和抬起的触控点位置计算点击或者上下左右滑动
  205. public void TapOrSwipe(InputAction.CallbackContext context)
  206. {
  207. if (context.phase == InputActionPhase.Started)
  208. {
  209. pressDownPosition = Pointer.current.position.ReadValue();
  210. pressDownTime = Time.time;
  211. return;
  212. }
  213. if (context.phase == InputActionPhase.Canceled)
  214. {
  215. pressUpPosition = Pointer.current.position.ReadValue();
  216. pressUpTime = Time.time;
  217. float deltaX = pressUpPosition.x - pressDownPosition.x;
  218. float deltaY = pressUpPosition.y - pressDownPosition.y;
  219. float distance = Mathf.Sqrt(deltaX * deltaX + deltaY * deltaY);
  220. // Debug.Log($"deltaX: {deltaX}, deltaY: {deltaY}, distance: {distance}");
  221. float timeDelta = pressUpTime - pressDownTime;
  222. string action;
  223. if (timeDelta > 0.2f)
  224. {
  225. return;
  226. }
  227. else
  228. {
  229. if (distance < 20f)
  230. {
  231. action = "tap";
  232. }
  233. else
  234. {
  235. if (Mathf.Abs(deltaX) > Mathf.Abs(deltaY))
  236. {
  237. if (deltaX > 0)
  238. {
  239. action = "right";
  240. }
  241. else
  242. {
  243. action = "left";
  244. }
  245. }
  246. else
  247. {
  248. if (deltaY > 0)
  249. {
  250. action = "up";
  251. }
  252. else
  253. {
  254. action = "down";
  255. }
  256. }
  257. }
  258. }
  259. TouchResponse touchResponse = new TouchResponse(action, pressUpTime);
  260. // Debug.Log($"TouchResponse: {touchResponse.action}, {touchResponse.time}");
  261. // 检测点击的音符
  262. ScoreCheck(touchResponse);
  263. }
  264. return;
  265. }
  266. private void ScoreCheck(TouchResponse touchResponse)
  267. {
  268. if (touchResponse != null)
  269. {
  270. foreach (var note in soundTrackManager.soundAction)
  271. {
  272. float TapTimeDelta = Mathf.Abs(touchResponse.time - (note.time + gameStartTime)); // 计算点击时间和音符时间的差值
  273. if (note.isActive && !note.isTapped && TapTimeDelta < 0.3f)
  274. {
  275. note.isTapped = true;
  276. if (touchResponse.action == note.action)
  277. {
  278. if (TapTimeDelta < 0.1f)
  279. {
  280. score += 100;
  281. combo++;
  282. if (combo > maxCombo)
  283. {
  284. maxCombo = combo;
  285. }
  286. perfectCount++;
  287. NGCombo = 0;
  288. // Debug.Log("Perfect!" + Time.time + " total perfect:" + perfectCount);
  289. string noteName = soundTrackManager.soundAction.IndexOf(note).ToString();
  290. DestroyNote(noteName);
  291. }
  292. else if (TapTimeDelta < 0.2f)
  293. {
  294. score += 30;
  295. combo = 0;
  296. goodCount++;
  297. NGCombo = 0;
  298. // Debug.Log("Good!" + Time.time + " total good:" + goodCount);
  299. string noteName = soundTrackManager.soundAction.IndexOf(note).ToString();
  300. DestroyNote(noteName);
  301. }
  302. else
  303. {
  304. score += 10;
  305. combo = 0;
  306. poorCount++;
  307. NGCombo++;
  308. // Debug.Log("Poor!" + Time.time + " total poor:" + poorCount);
  309. }
  310. }
  311. else
  312. {
  313. combo = 0;
  314. missCount++;
  315. NGCombo++;
  316. // Debug.Log("Wrong!" + Time.time + " total miss:" + missCount);
  317. }
  318. // Debug.Log("index of note:" + soundTrackManager.soundAction.IndexOf(note));
  319. break;
  320. }
  321. }
  322. }
  323. }
  324. private void DestroyNote(string noteName)
  325. {
  326. GameObject note = GameObject.Find("note_" + noteName);
  327. if (note != null)
  328. {
  329. Destroy(note);
  330. }
  331. else
  332. {
  333. Debug.Log("Note not found: " + noteName);
  334. }
  335. }
  336. // 设置狗的状态
  337. private void DogStatusUpdate()
  338. {
  339. GameObject[] dogObjects = GameObject.FindGameObjectsWithTag("dog");
  340. Debug.Log("combo:"+ combo);
  341. // combo = 6; // 这里是为了测试用,实际应该从游戏逻辑中获取当前连击数
  342. if (combo >= 5 && combo < 10)
  343. {
  344. currentDogState = "running";
  345. foreach (GameObject dog in dogObjects)
  346. {
  347. Animator animator = dog.GetComponent<Animator>();
  348. if (animator != null)
  349. {
  350. animator.SetTrigger("run");
  351. // animator.Play("Run");
  352. }
  353. }
  354. }
  355. else if (combo >= 10)
  356. {
  357. currentDogState = "fastRunning";
  358. foreach (GameObject dog in dogObjects)
  359. {
  360. Animator animator = dog.GetComponent<Animator>();
  361. if (animator != null)
  362. {
  363. animator.SetTrigger("fastRun");
  364. // animator.Play("Fast Run");
  365. }
  366. }
  367. }
  368. else if(combo == 0)
  369. {
  370. if (currentDogState == "running" || currentDogState == "fastRunning")
  371. {
  372. currentDogState = "walking";
  373. foreach (GameObject dog in dogObjects)
  374. {
  375. Animator animator = dog.GetComponent<Animator>();
  376. if (animator != null)
  377. {
  378. // animator.Play("Walk");
  379. animator.SetTrigger("walk");
  380. }
  381. }
  382. }
  383. }
  384. }
  385. }
  386. public class TouchResponse
  387. {
  388. public string action = ""; // 触控动作 up, down, left, right, tap, none(表示无效)
  389. public float time;
  390. public TouchResponse(string action, float time)
  391. {
  392. this.action = action;
  393. this.time = time;
  394. }
  395. }