前言
欢迎来到【制作100个Unity游戏】系列!本系列将引导您一步步学习如何使用Unity开发各种类型的游戏。在这第23篇中,我们将探索如何制作一个类似于七日杀和森林的生存游戏。
本篇内容会比较多,我会分几篇来实现,感兴趣的可以关注一下,以免错过内容更新。
本节主要引入泛型单例和添加各种游戏音效。
泛型单例
随着脚本越来越多,重复配置单例无疑是相对麻烦的,这里写一个通用的泛型单例脚本,节约时间,优化带
//简单的泛型单例 public class Singleton<T> : MonoBehaviour where T : Component { private static T instance; public static T Instance { get { if (instance == null) { instance = FindObjectOfType<T>(); if (instance == null) { GameObject newInstance = new GameObject(); instance = newInstance.AddComponent<T>(); } } return instance; } } public void Awake() { instance = this as T; } }
使用参考
public class AudioManager : Singleton<AudioManager> {}
添加声音
声音就大家自行去找了,网上都挺多的
脚步声
获取人物移动状态,修改PlayerState
[Header("判断人物状态")] public bool isMove;//是否在移动 public bool isWalking;//是否正在行走 public bool isRuning; //是否正在奔跑 public bool isCrouching; //是否正在下蹲 //。。。 private void Update() { isMove = MovementScript.Instance.isGround && MovementScript.Instance.moveDirection.sqrMagnitude > 0; isRuning = MovementScript.Instance.isRun && isMove; isCrouching = MovementScript.Instance.isCrouching; isWalking = !isRuning && !isCrouching && isMove; //。。。 }
脚步声控制
//脚步声 public class FootStepSound : MonoBehaviour { public LayerMask layerMask; // 检测层级 public RaycastHit hit; // 射线检测结果 public GameObject RayGo; // 射线起点对象 public AudioClip[] clipsMetal, clipsTree, clipsGrass, clipsDirt, clipsWater; // 不同类型表面的声音剪辑数组 public float dist = 2; // 射线的长度 private string tagProv; // 上一次射线检测到的地面标签 private int go = 0; // 控制播放声音的变量,0表示重新播放,1表示继续播放 public AudioSource AS; // 声音源组件 //该属性控制音频的播放速度,1.0 表示正常速度,小于 1.0 表示慢速播放,大于 1.0 表示快速播放。 public float PitchRun, PitchWalk, PitchCrouch; // 创建一个列表用于存储之前选择过的音效索引 private List<int> playedIndexes = new List<int>(); //判断child是否是parent的子集 bool IsChildOf(Transform child, Transform parent) { while (child != null) { if (child == parent) { return true; } child = child.parent; } return false; } void Update() { if (PlayerState.Instance.isRuning) AS.pitch = PitchRun; //则设置为奔跑音调 else if(PlayerState.Instance.isWalking){ AS.pitch = PitchWalk; }else{ AS.pitch = PitchCrouch; } if (PlayerState.Instance.isMove) { if (Physics.Raycast(RayGo.transform.position, Vector3.down, out hit, dist, layerMask)) // 向下发射射线检测地面 { if (hit.collider) { if (hit.collider.tag != tagProv) go = 0; tagProv = hit.collider.tag; switch (hit.collider.tag) // 根据地面的标签选择对应的声音类型 { case "Metal": PlayRandomSound(clipsMetal); // 播放金属声音 break; case "Tree": PlayRandomSound(clipsTree); // 播放树木声音 break; case "Grass": PlayRandomSound(clipsGrass); // 播放草地声音 break; case "Dirt": PlayRandomSound(clipsDirt); // 播放土地声音 break; case "Water": PlayRandomSound(clipsWater); // 播放水声音 break; default: StopSound(); // 停止播放声音 break; } } } } else { // StopSound(); // 停止播放声音 } } // 从给定的声音剪辑数组中随机播放一个声音 void PlayRandomSound(AudioClip[] clips) { if (go == 0) // 如果需要重新设置音频剪辑 { AS.clip = null; go = 1; } if (!AS.isPlaying) // 如果当前没有正在播放的声音 { // AS.clip = clips[Random.Range(0, clips.Length)]; // 随机选择一个声音剪辑 int randomIndex = GetUniqueRandomIndex(clips.Length); // 获取一个未播放过的随机索引 AS.clip = clips[randomIndex]; // 根据索引选择一个声音剪辑 AS.Play(); // 播放声音 } } // 获取一个未播放过的随机索引 int GetUniqueRandomIndex(int arrayLength) { int randomIndex; do { randomIndex = Random.Range(0, arrayLength); // 生成一个随机索引 } while (playedIndexes.Contains(randomIndex)); // 循环判断该索引是否已经播放过 playedIndexes.Add(randomIndex); // 将新的索引添加到已播放列表中 if (playedIndexes.Count >= arrayLength) { playedIndexes.Clear(); // 如果已播放列表包含所有索引,则清空列表,重新开始播放 } return randomIndex; } // 停止播放声音并重置go变量 void StopSound() { AS.Stop(); // 停止播放声音 go = 0; // 重置go变量 } }
配置地面层级和标签,目前先只有一个地面Grass
配置代码
鸭子动物音效
本来打算开启直接播放音效的,但是声音太过密集了,所以这里用修改代码随机播放声音
配置因为鸭子的声音是3D的,所以需要修改AudioSource配置
代码随机默认声音,修改AI_Movement
[Header("声音")] public AudioSource callAudioSource; void Start() { //。。。 StartCoroutine(PlaySound()); } IEnumerator PlaySound() { while (true) { yield return new WaitForSeconds(Random.Range(3, 10)); callAudioSource.Play(); } }
配置
人物各种操作音效
可以看看之前这篇文章:【unity小技巧】Unity音乐和音效管理器
public class AudioManager : Singleton<AudioManager> { //定义音乐和音效的Sound数组 public Sound[] musicSounds, sfxSounds; //音乐和音效的AudioSource public AudioSource musicSource, sfxSource; private void Start() { PlayMusic("森林环境"); } //播放音乐的方法,参数为音乐名称 public void PlayMusic(string name) { //从音乐Sounds数组中找到名字匹配的Sound对象 Sound s = Array.Find(musicSounds, x => x.name == name); //如果找不到对应的Sound,输出错误信息 if (s == null) { Debug.Log("没有找到音乐"); } //否则将音乐源的clip设置为对应Sound的clip并播放 else { musicSource.clip = s.clip; musicSource.Play(); } } //播放音效的方法,参数为音效名称 public void PlaySFX(string name) { //从音效Sounds数组中找到名字匹配的Sound对象 Sound s = Array.Find(sfxSounds, x => x.name == name); //如果找不到对应的Sound,输出错误信息 if (s == null) { Debug.Log("没有找到音效"); } //否则播放对应Sound的clip else { sfxSource.PlayOneShot(s.clip); } } }
配置参数
调用案例
AudioManager.Instance.PlaySFX("制作");
砍树音效
新增TreeSound,控制树声
//树声控制 public class TreeSound : MonoBehaviour { [SerializeField] private AudioClip[] hitSounds; // 击中树声音数组 [SerializeField] private AudioClip fallSound; // 倒下声音 private AudioSource audioSource; private void Start() { audioSource = GetComponent<AudioSource>(); } //击中树声音 public void PlayHitAudio() { int n = UnityEngine.Random.Range(1, hitSounds.Length); audioSource.clip = hitSounds[n]; // 随机选择一个声音 audioSource.PlayOneShot(audioSource.clip); //播放过的脚步声放置第一位,避免连续播放相同的脚步声 hitSounds[n] = hitSounds[0]; hitSounds[0] = audioSource.clip; } //倒下声音 public void PlayFallAudio() { audioSource.clip = fallSound; audioSource.Play(); } }
挂载在树预制体上
修改ChoppableTree,调用
private TreeSound treeSound; treeSound = GetComponent<TreeSound>(); treeSound.PlayHitAudio(); treeSound.PlayFallAudio();
效果
源码
源码不出意外的话我会放在最后一节