SoundGameController.cs 19 KB

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