关于u3d:U3D游戏开发框架六对象池

一:目标 内存治理是软件开发中重要的一项,在游戏中,有一些须要频繁创立和销毁的对象,例如射击游戏中的子弹,跑酷游戏中的障碍物等,通常的办法是应用Instantiate和Destroy,一直的开拓和开释内存,然而这样的操作是致命的,会产生内存碎片 什么是内存碎片:内存碎片意味着在堆中的空余空间被打碎成了很多小的内存碎片,而不是大的间断内存块,总共可用内存兴许很大,然而最长的间断空间可能很小很小例如下图所示,假如咱们最初还有20个空余字节,然而被一块正在应用的内存O2宰割成了两局部,这时咱们尝试调配15字节的对象就会失败,最终导致游戏解体所有咱们能够定义一个池对象,其中蕴含了一组可重用的对象当须要新对象时,向池子要一个,如果池子中没有此对象,就Instantiate一个,如果池子中存在此对象,则间接SetActive=true当须要销毁时,不应用Destroy,间接SetActive=false并放回池子通过这种形式能够轻易地创立和销毁对象而不用分配内存或其余资源 二:解决的问题及长处——防止分配内存和开释内存产生的内存碎片导致的游戏解体 三:应用——将所有预制体文件对立放在Resources文件夹下的Prefabs文件夹的子文件夹下——每个对象池物体的脚本继承ReusableObj类,并实现OnSpawn和OnUnSpawn办法,重写Reset办法——应用ObjectPoolMgr.Ins.XXX ObjectPoolMgr.Ins.Allocate("Effect");ObjectPoolMgr.Ins.Recycle(gameObject);四:代码实现 using System.Collections.Generic;using UnityEngine; /// <summary>/// 对象池管理器/// </summary>public class ObjectPoolMgr : Singleton<ObjectPoolMgr>{ public const string ResDir = "Prefabs/";//资源目录 private Dictionary<string, SubPool> poolCache = new Dictionary<string, SubPool>();//所有的池子 #region Main /// <summary> /// 分配资源 /// </summary> public GameObject Allocate(string resName) { if (!poolCache.ContainsKey(resName)) { if (!CreatePool(resName)) { Debug.LogError("创立池子失败:" + resName); return null; } } return poolCache[resName].Allocate(); } /// <summary> /// 回收资源 /// </summary> public void Recycle(GameObject obj) { string poolName = obj.GetComponent<ReuseableObj>().HostPoolId; poolCache[poolName].Recycle(obj); } #endregion /// <summary> /// 创立池子 /// </summary> private bool CreatePool(string resName) { string path = ResDir + resName; GameObject prefab = Resources.Load<GameObject>(path); if (prefab == null) { return false; } SubPool subPool = new SubPool(resName, prefab); poolCache.Add(resName, subPool); return true; }} /// <summary>/// 子池子/// </summary>public class SubPool{ public string poolId;//池子名称 public GameObject prefab;//预制体 public Stack<GameObject> unUsedObjCache = new Stack<GameObject>();//池子中所有未应用的对象 /// <summary> /// 初始化池子 /// </summary> public SubPool(string poolId, GameObject prefab) { this.poolId = poolId; this.prefab = prefab; } /// <summary> /// 分配资源 /// </summary> public GameObject Allocate() { GameObject obj = GetUnusedObj(); if (obj == null) { obj = SpawnObj(); } obj.GetComponent<ReuseableObj>().OnSpawn(); obj.SetActive(true); return obj; } /// <summary> /// 回收资源 /// </summary> public void Recycle(GameObject obj) { obj.SetActive(false); unUsedObjCache.Push(obj); obj.GetComponent<ReuseableObj>().OnUnSpawn(); obj.GetComponent<ReuseableObj>().Reset(); } /// <summary> /// 生成对象 /// </summary> public GameObject SpawnObj() { GameObject obj = Object.Instantiate(prefab); obj.GetComponent<ReuseableObj>().HostPoolId = poolId; return obj; } /// <summary> /// 从池子中失去一个未应用的对象 /// </summary> private GameObject GetUnusedObj() { GameObject obj = null; if (unUsedObjCache.Count <= 0) return null; obj = unUsedObjCache.Pop(); return obj; }}using UnityEngine; /// <summary>/// 对象池复用的物体/// </summary>public abstract class ReuseableObj : MonoBehaviour{ //隶属的池子 private string hostPoolId; public string HostPoolId { get { return hostPoolId; } set { hostPoolId = value; } } /// <summary> /// 取出时 /// </summary> public abstract void OnSpawn(); /// <summary> /// 回收时 /// </summary> public abstract void OnUnSpawn(); /// <summary> /// 重置 /// </summary> public abstract void Reset();}图片起源:http://www.hp91.cn/ 网页游戏 ...

April 19, 2021 · 2 min · jiezi

关于u3d:U3D游戏开发框架五计时器

一:目标 游戏开发中,计时是一个常见的性能。比方技能的冷却工夫,角色的生命回复等等 个别的实现办法是通过协程或者Invoke,然而这样并没有对立治理所有的计时器,所以咱们须要一个管理器对立治理计时器的相干操作 二:解决的问题及长处 ——注册和登记计时器——失去某一个计时器的倒计时剩余时间——设置所有倒计时同步或异步 三:应用办法 ——应用TimerMgr.Ins.XXX TimerMgr.Ins.RegisterTimer(3, () => { Debug.Log("finish 1"); }, "", true);TimerMgr.Ins.RegisterTimer(4, () =>{ Debug.Log("finish 2");}, "", true);TimerMgr.Ins.UnRegisterAllTimer();TimerMgr.Ins.UnRegisterTimer("Timer1");TimerMgr.Ins.GetRemainTime("Timer1");四:代码实现 using System.Collections.Generic;using UnityEngine;using System; /// <summary>/// 计时管理器/// </summary>public class TimerMgr : MonoSingleton<TimerMgr>{ //是否同步计时 private bool isSync = true; public bool IsSync { get { return isSync; } set { isSync = value; } } private List<TimerData> timerList = new List<TimerData>();//倒计时列表 private void Awake() { lastUpdateTime = Time.realtimeSinceStartup; } /// <summary> /// 注册倒计时 /// </summary> public void RegisterTimer(float countdown, Action onFinish = null, string timerName = "", bool ignoreTimeScale = false) { TimerData timerData = new TimerData(countdown, onFinish, timerName, ignoreTimeScale); timerList.Add(timerData); } /// <summary> /// 登记所有倒计时 /// </summary> public void UnRegisterAllTimer() { timerList.Clear(); } /// <summary> /// 登记某一个倒计时 /// </summary> public void UnRegisterTimer(string name) { List<TimerData> unRegisterCache = new List<TimerData>(); foreach (var timer in timerList) { if (timer.name == name) { unRegisterCache.Add(timer); } } for (int i = unRegisterCache.Count - 1; i >= 0; i--) { timerList.RemoveAt(i); } } /// <summary> /// 失去倒计时剩余时间 /// </summary> public float GetRemainTime(string name) { foreach (var timer in timerList) { if (timer.name == name) { return timer.Timer; } } Debug.LogWarning("没有注册倒计时:" + name); return -1; } float lastUpdateTime;//上一次的工夫 private void Update() { float realDeltaTime = Time.realtimeSinceStartup - lastUpdateTime; lastUpdateTime = Time.realtimeSinceStartup; if (isSync) { for (int i = 0; i < timerList.Count; i++) { timerList[i].UpdateTimer(timerList[i].ignoreTimeScale ? realDeltaTime : Time.deltaTime); if (timerList[i].isFinish) { timerList.Remove(timerList[i]); i--; } } } else { if (timerList.Count == 0) return; timerList[0].UpdateTimer(timerList[0].ignoreTimeScale ? realDeltaTime : Time.deltaTime); if (timerList[0].isFinish) { timerList.Remove(timerList[0]); } } }} /// <summary>/// 计时器的数据类/// </summary>public class TimerData{ //计时器 private float timer; public float Timer { get { return timer; } } public float countdown;//倒计时工夫 public string name;//倒计时的名称 public bool ignoreTimeScale;//是否疏忽工夫比例 public Action onFinish;//倒计时完结的回调 public bool isFinish;//是否实现倒计时 /// <summary> /// 初始化计时器 /// </summary> public TimerData(float countdown, Action onFinish, string name, bool ignoreTimeScale) { timer = countdown; this.countdown = countdown; this.onFinish = onFinish; this.name = name; this.ignoreTimeScale = ignoreTimeScale; } /// <summary> /// 更新倒计时 /// </summary> public void UpdateTimer(float deltaTime) { timer -= deltaTime; if (timer <= 0) { isFinish = true; onFinish?.Invoke(); } }}图片起源:http://www.coubai.com/ 网页游戏 ...

April 19, 2021 · 2 min · jiezi

关于u3d:U3D游戏开发框架四音频管理器

一:目标 游戏音频的播放在任何游戏中都占据十分重要的位置,音频的播放能够分为两种,一种为游戏音乐,另一种为游戏音效。前者实用于较长的音乐,如游戏背景音乐。第二种实用于比拟短的游戏音乐,如开枪霎时的音效在Unity中有两个对于音频的重要组件:AudioListener和AudioSource,通过AudioListener进行监听,AudioSource进行播放所以咱们须要一个管理器对立治理音频的相干操作 二:解决的问题及长处 ——音效的播放、暂停、持续、静音——背景音乐的播放、暂停、持续、静音、渐隐 三:应用办法 ——将所有音频文件对立放在Resources文件夹下的Sounds文件夹下——应用SoundMgr.Ins.XXX SoundMgr.Ins.PlayBGM("bgm");SoundMgr.Ins.PlaySound("coin");SoundMgr.Ins.StopPlay();SoundMgr.Ins.PausePlay();SoundMgr.Ins.BGMFade(2);四:代码实现 using UnityEngine; /// <summary>/// 音频管理器/// </summary>public class SoundMgr : MonoSingleton<SoundMgr>{ public const string SoundDir = "Sounds/";//音频目录 //BGM播放器 private AudioSource bgmPlayer; public AudioSource BgmPlayer { get { return bgmPlayer; } } //音效播放器 private AudioSource soundPlayer; public AudioSource SoundPlayer { get { return soundPlayer; } } //是否静音 private bool isMute; public bool IsMute { get { return isMute; } } private bool bgmFade;//背景音乐是否渐隐 private float bgmFadeBeginVolume;//背景音乐渐隐开始时的音量 private float bgmFadeBeginTime;//背景音乐渐隐开始的工夫 private float bgmFadeDuration;//背景音乐渐隐的工夫 private void Awake() { if (bgmPlayer == null) { bgmPlayer = gameObject.AddComponent<AudioSource>(); bgmPlayer.loop = true; bgmPlayer.playOnAwake = false; bgmPlayer.mute = isMute; } if (soundPlayer == null) { soundPlayer = gameObject.AddComponent<AudioSource>(); soundPlayer.loop = false; soundPlayer.playOnAwake = false; soundPlayer.mute = isMute; } } /// <summary> /// 播放音效 /// </summary> public void PlaySound(string soundName, float volume = 1, bool loop = false) { AudioClip clip = Resources.Load<AudioClip>(SoundDir + soundName); if (clip == null) { Debug.LogError("没有此音频:" + soundName); return; } soundPlayer.clip = clip; soundPlayer.volume = volume; soundPlayer.loop = loop; soundPlayer.Play(); } /// <summary> /// 播放背景音乐 /// </summary> public void PlayBGM(string bgmName, float volume = 1, bool loop = true) { AudioClip clip = Resources.Load<AudioClip>(SoundDir + bgmName); if (clip == null) { Debug.LogError("没有此音频:" + bgmName); return; } bgmPlayer.clip = clip; bgmPlayer.volume = volume; bgmPlayer.loop = loop; bgmPlayer.Play(); } /// <summary> /// 背景音乐渐隐 /// </summary> public void FadeBGM(float fadeDuration) { bgmFade = true; bgmFadeBeginTime = Time.realtimeSinceStartup; bgmFadeBeginVolume = bgmPlayer.volume; bgmFadeDuration = fadeDuration; } /// <summary> /// 暂停播放 /// </summary> public void PausePlay() { bgmPlayer.Pause(); soundPlayer.Pause(); } /// <summary> /// 持续播放 /// </summary> public void ResumePlay() { bgmPlayer.Play(); soundPlayer.Play(); } /// <summary> /// 进行播放 /// </summary> public void StopPlay() { bgmPlayer.Stop(); soundPlayer.Stop(); } /// <summary> /// 设置静音 /// </summary> public void SetMute(bool b) { isMute = b; bgmPlayer.mute = isMute; soundPlayer.mute = isMute; } /// <summary> /// 进行播放BGM /// </summary> public void StopPlayBGM() { bgmPlayer.Stop(); } /// <summary> /// 暂停播放BGM /// </summary> public void PausePlayBGM() { bgmPlayer.Pause(); } /// <summary> /// 持续播放BGM /// </summary> public void ResumePlayBGM() { bgmPlayer.Play(); } /// <summary> /// 进行播放音效 /// </summary> public void StopPlaySound() { soundPlayer.Stop(); } private void Update() { if (bgmFade) { float delta = Time.realtimeSinceStartup - bgmFadeBeginTime; if (delta <= bgmFadeDuration) { bgmPlayer.volume = Mathf.Lerp(bgmFadeBeginVolume, 0, delta / bgmFadeDuration); } else { bgmFade = false; bgmPlayer.volume = 0; } } }}图片起源:http://www.coubai.com/ 页游 ...

April 19, 2021 · 2 min · jiezi

关于u3d:U3D游戏开发框架三UI管理器

一:目标在制作游戏过程中,UI界面都是必不可少的,多则上百个少则几个,如果这些UI界面都放在场景里(如下图所示),那么运行的时候会占用大量的内存 个别都是将每一个UI界面制作成一个Prefab动静的进行显示暗藏,加载卸载所以咱们须要一个管理器对立治理UI的相干操作 二:解决的问题及长处 ——不须要创立UI画布和UI相机,动态创建并设置属性——治理每一个UI界面的层级——关上和敞开UI界面——查找某一个UI界面 三:应用办法 ——将所有UI制作成预制体对立放在Resources—Prefabs—UI文件夹下——所有的UI预制体都须要与挂载在身上的脚本名雷同——所有的UI预制体身上的脚本都须要继承UIBase基类——应用UIMgr.Ins.XXX UIMgr.Ins.OpenUI<UI_Main>();UIMgr.Ins.CloseUI<UI_Main>();UIMgr.Ins.TopUI;——依据不同我的项目需要设置UI画布的缩放:UIMgr.Ins.SetCanvasScaler() 四:代码实现 using UnityEngine;using UnityEngine.UI;using System.Collections.Generic;using UnityEngine.EventSystems; /// <summary>/// UI基类/// </summary>public class UIBase : MonoBehaviour{ /// <summary> /// 关上UI时 /// </summary> public virtual void OnView() { transform.SetAsLastSibling(); gameObject.SetActive(true); } /// <summary> /// 敞开UI时 /// </summary> public virtual void OnDisView() { gameObject.SetActive(false); }} /// <summary>/// UI管理器/// </summary>public class UIMgr : Singleton<UIMgr>{ public const string UIDir = "Prefabs/UI/";//UI目录 //渲染UI的画布 private Canvas uiCanvas; public Canvas UICanvas { get { return uiCanvas; } } //渲染UI的相机 private Camera uiCamera; public Camera UICamera { get { return uiCamera; } } //显示在最顶部的UI private UIBase topUI; public UIBase TopUI { get { return topUI; } } private Dictionary<string, UIBase> uiDict = new Dictionary<string, UIBase>();//场景中的UI protected override void Init() { base.Init(); if (uiCamera == null) { uiCamera = CreateUICamera(); } if (uiCanvas == null) { uiCanvas = CreateUICanvas(); } //设置Canvas缩放 SetCanvasScaler(new Vector2(750, 1334), false); } /// <summary> /// 关上UI /// </summary> public T OpenUI<T>(string uiRoot = "") where T : UIBase { string uiName = typeof(T).Name; if (!uiDict.ContainsKey(uiName)) { CreateUI(uiName, uiRoot); } UIBase ui = uiDict[uiName]; ui.OnView(); FindTopUI(); return ui as T; } /// <summary> /// 敞开UI /// </summary> public void CloseUI(string uiName, bool destroy = false) { if (!uiDict.ContainsKey(uiName)) { Debug.LogError("场景中没有此UI:" + uiName); return; } if (destroy) { GameObject.Destroy(uiDict[uiName].gameObject); uiDict.Remove(uiName); } else { uiDict[uiName].OnDisView(); } FindTopUI(); } /// <summary> /// 敞开UI /// </summary> public void CloseUI<T>(bool destroy = false) { string uiName = typeof(T).Name; if (!uiDict.ContainsKey(uiName)) { Debug.LogError("场景中没有此UI:" + uiName); return; } if (destroy) { GameObject.Destroy(uiDict[uiName].gameObject); uiDict.Remove(uiName); } else { uiDict[uiName].OnDisView(); } FindTopUI(); } /// <summary> /// 查找UI /// </summary> public T FindUI<T>() where T : UIBase { string uiName = typeof(T).Name; if (!uiDict.ContainsKey(uiName)) { Debug.LogError("场景中没有此UI:" + uiName); return default; } return uiDict[uiName] as T; } /// <summary> /// 查找最顶部的UI /// </summary> private void FindTopUI() { for (int i = uiCanvas.transform.childCount - 1; i >= 0; i--) { if (uiCanvas.transform.GetChild(i).GetComponent<UIBase>() != null && uiCanvas.transform.GetChild(i).gameObject.activeSelf) { topUI = uiCanvas.transform.GetChild(i).GetComponent<UIBase>(); break; } } } /// <summary> /// 设置Canvas缩放 /// </summary> /// <param name="referResolution">参考的分辨率</param> /// <param name="isLandscape">是否为横屏</param> public void SetCanvasScaler(Vector2 referResolution, bool isLandscape) { CanvasScaler canvasScaler = uiCanvas.GetComponent<CanvasScaler>(); canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; canvasScaler.matchWidthOrHeight = isLandscape ? 1 : 0; canvasScaler.referenceResolution = referResolution; } /// <summary> /// 创立UI /// </summary> private void CreateUI(string uiName, string uiRoot) { string path = UIDir + uiName; GameObject uiPrefab = Resources.Load<GameObject>(path); if (uiPrefab == null) { Debug.LogError("UI不存在:" + path); return; } UIBase uiBase = GameObject.Instantiate(uiPrefab, uiRoot == "" ? uiCanvas.transform : GameObject.Find(uiRoot).transform).GetComponent<UIBase>(); uiBase.gameObject.name = uiName; uiDict.Add(uiName, uiBase); } /// <summary> /// 创立UI画布 /// </summary> private Canvas CreateUICanvas() { GameObject uiCanvas = new GameObject("UICanvas"); uiCanvas.layer = 5; Canvas canvas = uiCanvas.AddComponent<Canvas>(); canvas.renderMode = RenderMode.ScreenSpaceCamera; canvas.worldCamera = uiCamera; uiCanvas.AddComponent<CanvasScaler>(); uiCanvas.AddComponent<GraphicRaycaster>(); GameObject eventSystem = new GameObject("EventSystem"); eventSystem.AddComponent<EventSystem>(); eventSystem.AddComponent<StandaloneInputModule>(); eventSystem.transform.SetParent(uiCanvas.transform); GameObject.DontDestroyOnLoad(uiCanvas); return uiCanvas.GetComponent<Canvas>(); } /// <summary> /// 创立UI相机 /// </summary> private Camera CreateUICamera() { GameObject uiCamera = new GameObject("UICamera"); Camera camera = uiCamera.AddComponent<Camera>(); camera.clearFlags = CameraClearFlags.Depth; camera.cullingMask = 1 << 5; camera.orthographic = true; camera.depth = 0; GameObject.DontDestroyOnLoad(uiCamera); return camera; }}图片起源:游戏加盟 ...

April 19, 2021 · 3 min · jiezi

关于u3d:U3D游戏开发框架二单例模版

一:目标当设计一个Manager时候,咱们心愿整个程序只有一个该Manager的对象实例,最先想到的实现办法是这样的 public class XXXManager{ private static XXXManager _instance = null; public static XXXManager Ins { get { if (_instance == null) { _instance = new XXXManager(); } return _instance; } }}如果每一个管理器类都实现一遍以上的单例代码会造成代码的反复,所以咱们要想方法把反复的代码抽离进去  二:解决的问题及长处 ——解决了实现单例代码的反复问题——动态创建空物体并挂载脚本,不须要手动创立物体和挂载脚本 三:应用办法 须要实现单例的类间接继承单例模版即可,有继承MonoBehaviour和不继承MonoBehaviour两种 public class GameMgr : MonoSingleton<GameMgr>{ } public class UIMgr : Singleton<UIMgr>{ }四:代码实现 /// <summary>/// 不继承Mono的单例模版/// </summary>public abstract class Singleton<T> where T : new(){ private static T _instance; public static T Ins { get { if (_instance == null) { _instance = new T(); (_instance as Singleton<T>).Init(); } return _instance; } } /// <summary> /// 子类初始化的一些操作 /// </summary> protected virtual void Init() { }}using UnityEngine; /// <summary>/// 继承Mono的单例模版/// </summary>public abstract class MonoSingleton<T> : MonoBehaviour where T : MonoSingleton<T>{ private static T _instance; public static T Ins { get { if (_instance == null) { GameObject go = null; T t = FindObjectOfType<T>(); if (t == null) { go = new GameObject(typeof(T).Name); _instance = go.AddComponent<T>(); } else { go = t.gameObject; go.name = typeof(T).Name; _instance = go.GetComponent<T>(); } DontDestroyOnLoad(go); } return _instance; } }}图片起源:手游 ...

April 19, 2021 · 1 min · jiezi

关于u3d:U3D游戏开发框架一什么是框架什么是库

一:前言 库能够了解为一个工具箱,实现了可重用的性能,一系列有用的类组合起来能够称之为类库框架为咱们提供了⼀个规定,你必须恪守他,框架的最大特色就是会接管程序的主控制流程,你只须要写业务逻辑代码,具体底层的执行由框架来调用 二:框架思维的演变 一开始接触U3D游戏开发,咱们会习惯把一些与GameObject没有显著关系的脚本(内存池,Manager等)挂载到一个空的GameObject上,应用的时候通过GameObject.Find()找到指标进行应用,这种形式称为EmptyGo,然而当咱们的我的项目规模越来越简单时,根本无法治理 当咱们接触了第一个设计模式:单例模式后,会想到能够把那些与GameObject没有显著关系的脚本对立放在一个单例治理类GameManager中,应用的时候间接GameManager.Ins拜访即可,这种形式称为Simple GameManager,然而当咱们的我的项目规模越来越简单时,GameManager的职责也随之变大,仍然不便于管理 思考过后,其实在一款游戏(页游,手游等等)中,与GameObject没有显著关系的逻辑管制是非常复杂的,所以能够把GameManager拆分为多个单例Manager,不同的Manager负责本人须要解决的逻辑,这种形式称为Manager of Managers

April 16, 2021 · 1 min · jiezi