C#重载运算符

 class Program
        {
            public int x;
            public int y;

            public static Program operator + (Program p1 , Program p2) {
                return new Program()
                {
                    x = p1.x + p2.x,
                    y = p1.y + p2.y
                };
            }
          

            static void Main(string[] args)
            {
                Program p1 = new Program() { x = 1, y = 3};
                Program p2 = new Program() { x = 4 , y = 2};
                Program p3 = p1 + p2;
                Console.WriteLine(p3.x);                            
            }
        }
    }

C#索引器

 class Program
        {
            private int[] test;
            public int this[int a] {
                get
                {
                    return test[a];
                }
                set
                {
                    if (test == null) {
                        test = new int[10];
                    }
                    test[a] = value;
                }                     
            }

            static void Main(string[] args)
            {
                Program p = new Program();
                p[5] = 10;
               
            }
        }

变长参数

参数前面加关键字params,参数必须为数组,传入的参数会添加进数组中。

变长参数必须作为最后一个参数使用,且只能有一个变长参数。

public int Sum(params int[] arr){
    int sum = 0;
    for(int i = 0; i<arr.Length; i++){
        sum += i;
}
    return sum;
}
Sum(1,2,3,4,5,6);

out和ref的区别

同点

可以在方法内部 影响外部传入的参数 让其可以被修改

区别

ref在函数调用前 初始化参数的变量

out在函数内部 初始化参数的变量

public void ChangeArray(ref int[] arr){
	arr[0] = 5;
}

public void ChangeArray(out int[] arr){
	arr = new int[]{5,4,3}
}

C#运算符重载

namespace 重载运算符
{
    class Box
    {
        public int length { get; set;}
        public int breadth { get; set;}
        public int height { get; set; }

        /// <summary>
        /// 运算符的重载
        /// </summary>
        /// <param name="box1"></param>
        /// <param name="box2"></param>
        /// <returns></returns>
        public static Box operator + (Box box1,Box box2) {
            Box box = new Box();
            box.length = box1.length + box2.length;
            box.breadth = box1.breadth + box2.breadth;
            box.height = box1.height + box2.height;
            return box;
        }

        public override string ToString()
        {
            return String.Format("长:{0},宽:{1},高:{2}",length,breadth,height);
        }
    }

  class Program
    {
        static void Main(string[] args)
        {
            Box box1 = new Box();
            box1.height = 5;
            box1.length = 4;
            box1.breadth = 3;
            Box box2 = new Box();
            box2.height = 2;
            box2.length = 3;
            box2.breadth = 2;
            Console.WriteLine(box1+box2);
            Console.ReadKey();
        }
    }
}

输出结果

长:7,宽:5,高:7

可重载和不可重载运算符

运算符描述
+, -, !, ~, ++, –这些一元运算符只有一个操作数,且可以被重载。
+, -, *, /, %这些二元运算符带有两个操作数,且可以被重载。
==, !=, <, >, <=, >=这些比较运算符可以被重载。
&&, ||这些条件逻辑运算符不能被直接重载。
+=, -=, *=, /=, %=这些赋值运算符不能被重载。
=, ., ?:, ->, new, is, sizeof, typeof这些运算符不能被重载。

线程安全是什么

  • 线程安全:当多个线程访问某一个类(对象或方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的。
  • 非线程安全:非线程主要是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改、值不同步的情况,进而影响程序的执行流程。

C#字符串比较

CompareTo 这是字符串比较的函数,用法:

string s1 = c;
string s2 = b;
if(s1.CompareTo(s2)==1)
{
}

它有三个返回值~:

  • 当s1>s2时,s1.CompareTo(s2)==1
  • 当s1=s2时,s1.CompareTo(s2)0
  • 当s1<s2时,s1.CompareTo(s2)-1

以上为例,c的asc大于b的asc,所以返回1

配置iptables

安装iptables

apt-get update
apt-get install iptables

放行端口

iptables -I INPUT -p tcp --dport 443 -j ACCEPT
iptables -I INPUT -p tcp --dport 80 -j ACCEPT
iptables -I INPUT -p tcp --dport 22 -j ACCEPT
iptables -I INPUT -p udp --dport 1024:65535 -j ACCEPT
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -P INPUT DROP
iptables -P FORWARD DROP

删除规则

iptables -L -n --line-number
iptables -D INPUT 3

保存规则

iptables-save

持久化规则

安装iptables-persistent

apt install iptables-persistent

保存规则

netfilter-persistent save
netfilter-persistent reload

ipv6防火墙

ip6tables -I INPUT -p tcp --dport 443 -j ACCEPT
ip6tables -I INPUT -p tcp --dport 80 -j ACCEPT
ip6tables -I INPUT -p tcp --dport 22 -j ACCEPT
ip6tables -A INPUT -p icmpv6 -j ACCEPT
ip6tables -I INPUT -p udp --dport 1024:65535 -j ACCEPT
ip6tables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
ip6tables -A INPUT -i lo -j ACCEPT
ip6tables -P INPUT DROP
ip6tables -P FORWARD DROP

apt安装nginx

添加安装源

nano /etc/apt/sources.list

加入两行

deb http://nginx.org/packages/debian/ bullseye nginx
deb-src http://nginx.org/packages/debian/ bullseye nginx

导入key

wget http://nginx.org/keys/nginx_signing.key
apt-key add nginx_signing.key

更新apt包

apt update -y

安装nginx

debian安装cloud内核

搜索内核

apt search linux-image | grep cloud-amd64

apt安装cloud内核

apt update && apt upgrade
apt install linux-image-cloud-amd64
reboot

卸载旧内核

uname -sr
Linux 4.12.0-041200-generic

列出系统安装的内核

dpkg -l | grep linux-image | awk '{print$2}'
linux-image-4.12.0-041200-generic
linux-image-4.8.0-22-generic
linux-image-extra-4.8.0-22-generic
linux-image-generic

卸载旧内核

Ecs框架学习笔记

ECS是一种软件架构模式,由三个元素组成:实体(Entity),组件(Component)和系统(System)(看起来和MVC很相似)。游戏程序分为这三个主要元素,并且通过定义每个系统的责任和关系来管理游戏。

ECS使用的是一种面向数据(OOD)的编程思想

比如:现在有一群狗和一群猪,我们要让它们的尾巴摇起来:

  • 面向过程:1、摇(所有狗的尾巴);2、摇(所有猪的尾巴)
  • 面向对象:1、所有狗.(摇尾巴);2、所有猪.(摇尾巴)
  • 面向数据:1、收集所有的尾巴;2、摇(尾巴)

为什么使用面向数据的编程思想?

相比于面向对象编程 面向数据拥有更好的内存利用率以及更好的性能

实体组件系统

组件(Component)只包含数据不包含方法
实体(Entity)组件的容器 一个实体可以对应多个组件
系统(System)只包含方法 对数据(组件)进行处理

Entitas简介

Entitas是专门用于Unity引擎的一款超快速、轻量级实体组件系统(ECS)。它经过了精心设计,不仅可以快速访问组件,也可以在垃圾收集环境中以最佳方式工作。

Entitas基本概念:

  1. Entity表示一个实体,用来存储数据。
  2. Context表示上下文环境,负责创建和销毁Entity。
  3. Group表示组,将相同类型的Entity归纳到一起。
  4. Matcher表示匹配查找,用来从Context中获取感兴趣的Group。
  5. Collector表示采集,用来从Group中收集变化的Entity。
  6. System表示系统,用来处理逻辑的。通常存在五种类型,分别如下:
    1. ->.IInitializeSystem只在初始化时调用一次。初始化实现可以写在Initialize方法中。
    2. ->.IExecuteSystem会在每帧执行一次。执行逻辑可以写在Execute方法中。
    3. ->.ICleanupSystem会在别的System完成后,每帧执行一次。回收逻辑可以写在Cleanup方法中。
    4. ->.ReactiveSystem会在Group有变化时执行一次。执行逻辑可以写在Execute方法中。
    5. ->Feature会将上述的System进行整合以及创建。

实现简单摇杆

using ProjectBase.EventCenter;
using ProjectBase.UI;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using EventType = ProjectBase.EventCenter.EventType;

namespace UI.JoyStick
{
    public class JoystickPanel : BasePanel
    {
        private Image mJoystickBg;
        private Image mJoystickControl;
        private Image mJoystickPanel;

        /// <summary>
        /// 遥杆初始位置
        /// </summary>
        private Vector2 mInitLocalPos;

        /// <summary>
        /// 摇杆按钮移动的最大位置
        /// </summary>
        private Vector2 mMaxLoaclPos;

        /// <summary>
        /// 鼠标位置在Bg下的局部坐标
        /// </summary>
        private Vector2 mLocalPos;


        /// <summary>
        /// 鼠标位置在面板下的局部坐标
        /// </summary>
        private Vector2 mBgLocalPos;


        void Start()
        {
            GetChildControl<Image>();
            mJoystickBg = GetControl<Image>("JoystickBg");
            mJoystickControl = GetControl<Image>("JoystickControl");
            mJoystickPanel = GetControl<Image>("JoystickPanel");
            mInitLocalPos = mJoystickBg.rectTransform.localPosition;
            //为面板添加自定义事件
            UIMgr.Instance.AddCustomizeEventListener(gameObject, EventTriggerType.PointerDown,
                OnPointerDown);
            UIMgr.Instance.AddCustomizeEventListener(gameObject, EventTriggerType.PointerUp, OnPointerUp);
            UIMgr.Instance.AddCustomizeEventListener(gameObject, EventTriggerType.Drag, OnDrag);
        }

        /// <summary>
        /// 更新位置数据
        /// </summary>
        /// <param name="data"></param>
        private void UpdatePos(BaseEventData data)
        {
            mLocalPos = UIMgr.Instance.ScreenPointToLocalPoint(data as PointerEventData, mJoystickBg.rectTransform);
            mMaxLoaclPos = mLocalPos.normalized * mJoystickBg.rectTransform.rect.width * 0.5f;
            mBgLocalPos =
                UIMgr.Instance.ScreenPointToLocalPoint(data as PointerEventData, mJoystickPanel.rectTransform);
        }

        private void OnPointerDown(BaseEventData data)
        {
            UpdatePos(data);
            if (mLocalPos.sqrMagnitude > mMaxLoaclPos.sqrMagnitude)
            {
                //如果超过最大距离 让摇杆在手指下方
                mJoystickBg.rectTransform.localPosition = mBgLocalPos;
            }
        }

        private void OnPointerUp(BaseEventData data)
        {
            mJoystickControl.transform.localPosition = Vector2.zero;
            mJoystickBg.rectTransform.localPosition = mInitLocalPos;
            //分发摇杆抬起事件
            EventMgr.Instance.EventTrigger(EventType.JoyStickUp, Vector2.zero);
        }

        private void OnDrag(BaseEventData data)
        {
            UpdatePos(data);
            if (mLocalPos.sqrMagnitude > mMaxLoaclPos.sqrMagnitude)
            {
                mJoystickControl.transform.localPosition = mMaxLoaclPos;
                //让摇杆跟着手指
                mJoystickBg.transform.localPosition = mBgLocalPos - mMaxLoaclPos;
            }
            else
            {
                mJoystickControl.transform.localPosition = mLocalPos;
            }
            //分发摇杆拖拽事件
            EventMgr.Instance.EventTrigger(EventType.JoyStickUpdate, mLocalPos.normalized);
        }
    }
}

测试

using ProjectBase.EventCenter;
using ProjectBase.UI;
using UI.JoyStick;
using UnityEngine;
using EventType = ProjectBase.EventCenter.EventType;

public class Move : MonoBehaviour
{
    public float Speed = 20;
    private Vector3 mDir;
    private bool mIsMove;

    void Start()
    {
        UIMgr.Instance.ShowPanel<JoystickPanel>("JoystickPanel",EUiLayer.Top);
        EventMgr.Instance.AddEventListener<Vector2>(EventType.JoyStickUpdate, (data) =>
        {
            mIsMove = true;
            ChangeDir(data);
        });
        EventMgr.Instance.AddEventListener<Vector2>(EventType.JoyStickUp, (data) => { mIsMove = false; });
    }


    void Update()
    {
        if (mIsMove)
        {
            // transform.LookAt(transform.position + mDir);
            transform.rotation = Quaternion.LookRotation(mDir);
            // transform.Translate(Vector3.forward*Time.deltaTime*Speed,Space.Self);//通过自身的坐标向前移动
            transform.Translate(mDir*Time.deltaTime*Speed,Space.World);//通过世界坐标
        }
    }

    private void ChangeDir(Vector2 vector2)
    {
        mDir.x = vector2.x;
        mDir.z = vector2.y;
    }
}

使用了前面写的事件中心和UI框架,要注意不同坐标系之间的转换,比较容易弄乱。

音频管理模块

SoundMgr

using System.Collections.Generic;
using ProjectBase.Res;
using ProjectBase.SingleBase;
using UnityEngine;

namespace ProjectBase.Sound
{
    /// <summary>
    /// 声音管理
    /// </summary>
    public class SoundMgr : Single<SoundMgr>
    {
        private string mBgmPath = "Audios/";
        private string mSoundPath = "Audios/";
        private AudioSource mBgm;
        private GameObject mBgmObj;
        private GameObject mSoundObj;
        private Dictionary<string, AudioSource> mSoundDict = new Dictionary<string, AudioSource>();


        /// <summary>
        /// 修改背景音乐音量
        /// </summary>
        public void SetBgmVolume(float volume)
        {
            mBgm.volume = volume;
        }

        /// <summary>
        /// 修改音效音量
        /// </summary>
        /// <param name="volume"></param>
        public void SetSoundsVolume(float volume)
        {
            foreach (var value in mSoundDict)
            {
                value.Value.volume = volume;
            }
        }

        /// <summary>
        /// 播放背景音乐
        /// </summary>
        /// <param name="name">音乐名</param>
        public void PlayNewBgm(string name)
        {
            if (mBgmObj == null)
            {
                mBgmObj = new GameObject("BgmObject");
                mBgm = mBgmObj.AddComponent<AudioSource>();
            }

            ResMgr.Instance.LoadAsync<AudioClip>(mBgmPath + name, (clip) =>
            {
                mBgm.clip = clip;
                mBgm.loop = true;
                mBgm.Stop();
                mBgm.Play();
            });
        }

        /// <summary>
        /// 继续播放当前BGM
        /// </summary>
        public void PlayBgm()
        {
            if (mBgm != null && mBgm.isPlaying)
            {
                mBgm.Play();
            }
        }

        /// <summary>
        /// 暂停当前BGM
        /// </summary>
        public void PauseBgm()
        {
            if (mBgm != null && mBgm.isPlaying)
            {
                mBgm.Pause();
            }
        }

        /// <summary>
        /// 停止播放BGM
        /// </summary>
        public void StopBgm()
        {
            if (mBgm != null && mBgm.isPlaying)
            {
                mBgm.Stop();
            }
        }


        /// <summary>
        /// 播放音效
        /// </summary>
        /// <param name="name"></param>
        /// <param name="loop"></param>
        public void PlaySound(string name, bool loop = false)
        {
            if (mSoundObj == null)
            {
                mSoundObj = new GameObject("SoundObject");
            }
            if (mSoundDict.ContainsKey(name))
            {
                mSoundDict[name].Stop();
                mSoundDict[name].Play();
            }
            else
            {
                ResMgr.Instance.LoadAsync<AudioClip>(mSoundPath + name, (clip) =>
                {
                    AudioSource audioSource = mSoundObj.AddComponent<AudioSource>();
                    mSoundDict.Add(name, audioSource);
                    audioSource.clip = clip;
                    audioSource.loop = loop;
                    audioSource.Play();
                });
            }
        }

        /// <summary>
        /// 停止播放音效
        /// </summary>
        /// <param name="name"></param>
        public void StopSound(string name)
        {
            if (mSoundDict.ContainsKey(name))
            {
                mSoundDict[name].Stop();
            }
        }

        /// <summary>
        /// 停止播放所有音效
        /// </summary>
        public void StopSounds()
        {
            foreach (var value in mSoundDict)
            {
                value.Value.Stop();
            }
        }
    }
}

AssetBundle学习笔记

AssetBundle(AB包)是什么?

AB包是特定于平台的资产压缩包(模型、贴图、预制件、音效、材质球等)

  • AB包可以更好的管理资源相对于Resources
  • Resources下的资源是只读的 无法修改
  • AB包的存储位置可以自定义
  • 压缩方式自定义
  • 可以对资源进行热更新
  • C#脚本不能被打包

减少包体大小的方法

  • 压缩资源
  • 减少初始包大小(从服务器端获取资源)

AssetBundle打包(AssetBundles-Browser插件)

Build打包设置

BuildTarget 目标平台

OutPut Path 输出路径

Clear Folders 是否清空文件夹 重新打包

Copy to StreamingAssets 是否拷贝到StreamingAssets文件夹

  • StreamingAssets–存放媒体文件,如视频等。该文件夹下的资源会打包进发布包,它和Resources文件夹的区别是:Resources文件夹下的资源在打包时会被压缩,但StreamingAssets文件夹下的资源不会被压缩,而是原封不动的打包进发布包中。另外需要注意:此路径在PC/Mac电脑上可对文件实施“增删改查”等操作(在Windows8系统上进行增删等写入操作时报路径不允许访问),但在移动端只支持读取操作。(只能放在Assets根目录下,有且仅有一个)

AdvancedSettings 高级设置

Compression(压缩方式)

  • No Compression(不压缩)
  • Standard Compression(LZMA)
    • 解加载时间更快
  • Chunk Based Compression(LZ4)
    • 内存占用更低
    • 解压缩时间更快

Exclude Type Information 在资源包中 是否包含资源的类型信息

Force Rebuild 重新打包时 是否重新构建包 和Clear Folders不同,它不会删除不再存在的包

Ignore Type Tree Changes 增量构建检查时 是否忽略类型数的更改

Append Hash 是否将文件的哈希值附加到资源包名上

Strict Mode 严格模式,如果打包时报错了,则打包直接失败

Dry Run Build 运行时构建


AB包相关方法

加载AB包

AssetBundle资源管理器

AbMgr

using System.Collections;
using System.Collections.Generic;
using ProjectBase.Mono;
using ProjectBase.SingleBase;
using UnityEngine;
using UnityEngine.Events;

namespace ProjectBase.Res
{
    /// <summary>
    /// AB包资源加载
    /// </summary>
    public class AbMgr : Single<AbMgr>
    {
        /// <summary>
        /// AB包字典
        /// </summary>
        Dictionary<string, AssetBundle> mAbDict = new Dictionary<string, AssetBundle>();

        /// <summary>
        /// AB包主包
        /// </summary>
        private AssetBundle mMainAb;

        /// <summary>
        /// 主包AssetBundleManifest对象
        /// </summary>
        private AssetBundleManifest mMainAbm;

        /// <summary>
        /// AB包的存放路径
        /// </summary>
        private string mPathUrl => Application.streamingAssetsPath + "/";

        /// <summary>
        /// 主包路径
        /// </summary>
        private string mMainUrl
        {
            get
            {
#if UNITY_IOS
                return "IOS"+"/";
#elif UNITY_EDITOR_OSX
                return "StandaloneOSXUniversal";
#else
                return "PC"+"/";
#endif
            }
        }

        /// <summary>
        /// 同步加载AB包
        /// </summary>
        /// <param name="abName"></param>
        /// <returns></returns>
        private AssetBundle Load(string abName)
        {
            LoadRely(abName);
            if (!mAbDict.ContainsKey(abName))
            {
                mAbDict.Add(abName, AssetBundle.LoadFromFile(mPathUrl + abName));
            }

            return mAbDict[abName];
        }

        /// <summary>
        /// 同步加载AB包依赖
        /// </summary>
        /// <param name="abName"></param>
        private void LoadRely(string abName)
        {
            if (mMainAb == null)
            {
                mMainAb = AssetBundle.LoadFromFile(mPathUrl + mMainUrl);
                mMainAbm = mMainAb.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
            }

            string[] names = mMainAbm.GetAllDependencies(abName);
            foreach (var value in names)
            {
                if (mAbDict.ContainsKey(value)) continue; //如果已经加载了 就不再加载
                mAbDict.Add(value, AssetBundle.LoadFromFile(mPathUrl + value));
            }
        }

        /// <summary>
        /// 异步加载AB包
        /// </summary>
        /// <param name="abName"></param>
        /// <param name="callBack"></param>
        private void LoadAsync(string abName, UnityAction<AssetBundle> callBack)
        {
            MonoManager.Instance.StartCoroutine(IELoadAsync(abName, callBack));
        }

        /// <summary>
        /// 异步加载AB包协程
        /// </summary>
        /// <param name="abName"></param>
        /// <param name="callBack"></param>
        /// <returns></returns>
        private IEnumerator IELoadAsync(string abName, UnityAction<AssetBundle> callBack)
        {
            Coroutine coroutine = MonoManager.Instance.StartCoroutine(IELoadRelyAsync(abName));
            yield return coroutine;
            if (!mAbDict.ContainsKey(abName))
            {
                AssetBundleCreateRequest abcq = AssetBundle.LoadFromFileAsync(mPathUrl + abName);
                yield return abcq;
                if (abcq.assetBundle == null) yield break;
                mAbDict.Add(abName, abcq.assetBundle);
                callBack.Invoke(abcq.assetBundle);
                yield break;
            }

            callBack.Invoke(mAbDict[abName]);
        }

        /// <summary>
        /// 异步加载AB包依赖携程
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        private IEnumerator IELoadRelyAsync(string abName)
        {
            //异步加载主包
            if (mMainAb == null)
            {
                var abcq = AssetBundle.LoadFromFileAsync(mPathUrl + mMainUrl);
                yield return abcq;
                mMainAb = abcq.assetBundle;
                var assetBundleRequest = abcq.assetBundle.LoadAssetAsync<AssetBundleManifest>("AssetBundleManifest");
                yield return assetBundleRequest;
                mMainAbm = assetBundleRequest.asset as AssetBundleManifest;
            }

            string[] names = mMainAbm.GetAllDependencies(abName);
            foreach (var value in names)
            {
                if (mAbDict.ContainsKey(value)) continue; //如果已经加载了 就不再加载
                var request = AssetBundle.LoadFromFileAsync(mPathUrl + value);
                yield return request;
                mAbDict.Add(value, request.assetBundle);
            }
        }

        /// <summary>
        /// 同步加载AB包中的资源
        /// </summary>
        /// <param name="abName"></param>
        /// <param name="resName"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public T LoadAsset<T>(string abName, string resName) where T : Object
        {
            return !mAbDict.ContainsKey(abName) ? Load(abName).LoadAsset<T>(resName) : mAbDict[abName].LoadAsset<T>(resName);
        }

        /// <summary>
        /// 异步加载AB包中的资源
        /// </summary>
        /// <param name="abName"></param>
        /// <param name="resName"></param>
        /// <param name="callBack"></param>
        /// <typeparam name="T"></typeparam>
        public void LoadAssetAsync<T>(string abName, string resName, UnityAction<T> callBack) where T : Object
        {
            if (!mAbDict.ContainsKey(abName))
            {
                LoadAsync(abName,
                    (ab) => { MonoManager.Instance.StartCoroutine(IELoadAssetAsync(ab, resName, callBack)); });
            }
            else
            {
                MonoManager.Instance.StartCoroutine(IELoadAssetAsync(mAbDict[abName], resName, callBack));
            }
        }

        /// <summary>
        /// 异步加载AB包中的资源协程
        /// </summary>
        /// <param name="assetBundle"></param>
        /// <param name="resName"></param>
        /// <param name="callBack"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        private IEnumerator IELoadAssetAsync<T>(AssetBundle assetBundle, string resName, UnityAction<T> callBack)
            where T : Object
        {
            var abq = assetBundle.LoadAssetAsync<T>(resName);
            yield return abq;
            if (abq.asset == null) yield break;
            callBack.Invoke(abq.asset as T);
        }

        /// <summary>
        /// 卸载AB包
        /// </summary>
        /// <param name="name"></param>
        /// <param name="unloadObjects"></param>
        public void UnLoad(string name, bool unloadObjects = false)
        {
            if (!mAbDict.ContainsKey(name)) return;
            mAbDict[name].Unload(unloadObjects);
            mAbDict.Remove(name);
        }

        /// <summary>
        /// 卸载所有ab包
        /// </summary>
        public void UnLoadAll(bool unloadObjects = false)
        {
            AssetBundle.UnloadAllAssetBundles(unloadObjects);
            mAbDict.Clear();
        }
    }
}

测试

//同步加载
        Instantiate(AbMgr.Instance.LoadAsset<GameObject>("cube", "cube2"));
        //异步加载
        AbMgr.Instance.LoadAssetAsync<GameObject>("cube", "cube2", (obj =>
        {
            Instantiate(obj);
        }));

UI框架2

BasePanelUI面板基类
UIMgrUI管理类
EUiLayerUI层级枚举

UIMgr

using System.Collections.Generic;
using ProjectBase.Res;
using ProjectBase.SingleBase;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;

namespace ProjectBase.UI
{
    /// <summary>
    /// UI面板层级枚举
    /// </summary>
    public enum EUiLayer
    {
        Top,
        Mid,
        Bot
    }

    /// <summary>
    /// UI面板管理器
    /// </summary>
    public class UIMgr : Single<UIMgr>
    {
        /// <summary>
        /// Canvas
        /// </summary>
        private RectTransform mCanvas;

        /// <summary>
        /// 顶层
        /// </summary>
        private Transform mTop;

        /// <summary>
        /// 中间层
        /// </summary>
        private Transform mMid;

        /// <summary>
        /// 底层
        /// </summary>
        private Transform mBot;

        /// <summary>
        /// UI路径
        /// </summary>
        private string mPath = "Prefabs/UI/UIPanel/";

        /// <summary>
        /// 面板字典
        /// </summary>
        private Dictionary<string, BasePanel> mPaelDict = new Dictionary<string, BasePanel>();

        /// <summary>
        /// 构造方法
        /// </summary>
        public UIMgr()
        {
            GameObject ui = Object.Instantiate(ResMgr.Instance.Load<GameObject>("Prefabs/UI/UI"));
            Object.DontDestroyOnLoad(ui);
            mCanvas = ui.transform.Find("Canvas").transform as RectTransform;
            mTop = GetCanvas.Find("Top");
            mMid = GetCanvas.Find("Mid");
            mBot = GetCanvas.Find("Bot");
        }

        /// <summary>
        /// 显示面板
        /// </summary>
        /// <param name="name">面板名</param>
        /// <param name="layer">面板层级</param>
        /// <typeparam name="T"></typeparam>
        public void ShowPanel<T>(string name, EUiLayer layer = EUiLayer.Mid)
            where T : BasePanel
        {
            if (mPaelDict.ContainsKey(name))
            {
                mPaelDict[name].transform.SetParent(GetLayer(layer));
                mPaelDict[name].Show();
            }
            else
            {
                //异步加载面板
                ResMgr.Instance.LoadAsync<GameObject>(mPath + name, (obj) =>
                {
                    GameObject panelObj = Object.Instantiate(obj, GetLayer(layer), false);
                    panelObj.name = obj.name; //方便UI面板下的脚本获取name
                    T panel = panelObj.GetComponent<T>();
                    mPaelDict.Add(name, panel);
                    panel.Show();
                });
            }
        }


        /// <summary>
        /// 隐藏面板
        /// </summary>
        /// <param name="name">面板名</param>
        public void HidePanel(string name)
        {
            if (!mPaelDict.ContainsKey(name)) return;
            mPaelDict[name].Hide();
            Object.Destroy(mPaelDict[name].gameObject);
            mPaelDict.Remove(name);
        }

        /// <summary>
        /// 获得Canvas
        /// </summary>
        public RectTransform GetCanvas => mCanvas;

        /// <summary>
        /// 获得面板
        /// 调用ShowPanel显示的面板才能被获取
        /// </summary>
        /// <param name="name">面板名</param>
        /// <returns></returns>
        public BasePanel GetPanel(string name) => mPaelDict.ContainsKey(name) ? mPaelDict[name] : null;


        /// <summary>
        /// 获得层级Transform
        /// </summary>
        /// <param name="layer"></param>
        /// <returns></returns>
        public Transform GetLayer(EUiLayer layer)
        {
            Transform uiLayer = null;
            switch (layer)
            {
                case EUiLayer.Top:
                    uiLayer = mTop;
                    break;
                case EUiLayer.Mid:
                    uiLayer = mMid;
                    break;
                case EUiLayer.Bot:
                    uiLayer = mBot;
                    break;
            }

            return uiLayer;
        }

        /// <summary>
        /// 添加自定义事件
        /// </summary>
        /// <param name="obj">添加事件的物体</param>
        /// <param name="type">事件类型</param>
        /// <param name="callBack">事件回调</param>
        public void AddCustomizeEventListener(GameObject obj, EventTriggerType type,
            UnityAction<BaseEventData> callBack)
        {
            EventTrigger trigger = obj.GetComponent<EventTrigger>();
            if (trigger == null)
            {
                trigger = obj.AddComponent<EventTrigger>();
            }

            foreach (var value in trigger.triggers)
            {
                if (value.eventID != type) continue;
                value.callback.AddListener(callBack);
                return;
            }

            EventTrigger.Entry entry = new EventTrigger.Entry {eventID = type};
            entry.callback.AddListener(callBack);
            trigger.triggers.Add(entry);
        }

        /// <summary>
        /// 删除自定义事件
        /// </summary>
        /// <param name="obj">添加事件的物体</param>
        /// <param name="type">事件类型</param>
        /// <param name="callBack">移除的函数</param>
        public void RemoveCustomizeEventListener(GameObject obj, EventTriggerType type,
            UnityAction<BaseEventData> callBack = null)
        {
            EventTrigger trigger = obj.GetComponent<EventTrigger>();
            if (trigger == null || trigger.triggers.Count == 0) return;
            foreach (var value in trigger.triggers)
            {
                if (value.eventID != type) continue;
                if (callBack == null)
                {
                    value.callback.RemoveAllListeners();
                }
                else
                {
                    value.callback.RemoveListener(callBack);
                }

                break;
            }
        }

        /// <summary>
        /// 屏幕坐标转局部坐标
        /// </summary>
        /// <param name="data"></param>
        /// <param name="transform"></param>
        /// <returns></returns>
        public Vector2 ScreenPointToLocalPoint(PointerEventData data, RectTransform transform)
        {
            RectTransformUtility.ScreenPointToLocalPointInRectangle(transform,
                data.position,
                data.enterEventCamera,
                out var localPos
            );
            return localPos;
        }
    }
}

Baseanel

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

namespace ProjectBase.UI
{
    /// <summary>
    /// UI面板基类
    /// </summary>
    public class BasePanel : MonoBehaviour
    {
        /// <summary>
        /// 组件字典
        /// 存储面板子物体里的组件
        /// </summary>
        private Dictionary<string, List<UIBehaviour>> mControlDict = new Dictionary<string, List<UIBehaviour>>();

        /// <summary>
        /// 从字典获取面板中子物体上的组件
        /// </summary>
        /// <param name="objectName">子物体名字</param>
        /// <typeparam name="T">组件类型</typeparam>
        /// <returns></returns>
        public T GetControl<T>(string objectName) where T : UIBehaviour
        {
            if (mControlDict.ContainsKey(objectName))
            {
                List<UIBehaviour> uiBehaviours = mControlDict[objectName];
                foreach (var t in uiBehaviours)
                {
                    if (t as T)
                    {
                        return t as T;
                    }
                }
            }
            return null;
        }

        /// <summary>
        /// 获得自身以及子物体上的组件 并存入字典
        /// </summary>
        /// <typeparam name="T">组件类型</typeparam>
        protected void GetChildControl<T>() where T : UIBehaviour
        {
            T[] t = gameObject.GetComponentsInChildren<T>();
            string objectName;
            foreach (var t1 in t)
            {
                objectName = t1.gameObject.name;
                if (!mControlDict.ContainsKey(objectName))
                {
                    mControlDict.Add(objectName, new List<UIBehaviour>());
                }

                mControlDict[objectName].Add(t1);
            }
        }
        
        /// <summary>
        /// 在面板显示
        /// </summary>
        public virtual void Show()
        {
        }

        /// <summary>
        /// 在面板退出
        /// </summary>
        public virtual void Hide()
        {
        }
    }
}

Tips

  • 要把层级物体的锚点设置在屏幕的四角 也就是让层级物体的大小和屏幕相同 否则层级面板下的物体会变形
  • EventTrigger不是ui组件 因为它并不继承自UIBehaviours
  • 不要直接删除EventTrigger.triggers列表中的元素 会报错

Unity中的特殊文件夹及脚本编译顺序

特殊文件夹

  • Standard Assets–存放Unity的标准资产。(只能放在Assets根目录下,有且仅有一个)
  • Pro Standard Assets–存放Unity的专业级标准资产。(只能放在Assets根目录下,有且仅有一个)
  • ActionScript–存放Flash的ActionScript脚本。当游戏被导出为Flash格式时,这里的脚本会自动替换指定的C#脚本。
  • Plugins–存放插件。该文件夹有很多用途,须详细了解可以参考官方文档。(只能放在Assets根目录下,有且仅有一个)
    • Plugins/Android–存放Android插件,包括 .jar 或XML文件等。
    • Plugins/iOS–存放iOS插件,包括 .a 或 .m、.mm文件等。
  • Editor–存放编辑器脚本。该文件夹里的所有资源文件或脚本文件都不会被打包进发布包中,并且脚本也只能在编辑器中使用。(可以放在Assets根目录下,也可以放在其它子目录里,数量不限)
  • Editor Default Resources–存放编辑器用到的资源,如图片、文本文件等。它里面的资源不会被打包进发布包中,我们可以通过EditorGUIUtility.Load方法读取该文件夹下的资源。(只能放在Assets根目录下,有且仅有一个)
  • Resources–存放需要动态读取的资源。该文件夹下的资源不管你用不用,它都会被打包进发布包中。在打包前可以用AssetDatabase.MoveAsset方法把不必要的资源从文件夹中移出去;打完包后再把移出去的资源移回来。(可以放在Assets根目录下,也可以放在其它子目录里,数量不限)
  • Gizmos–通常是存放TIF格式的图片,在OnDrawGizmos方法内使用Gizmos.DrawIcon将其画为图标在场景中显示。(只能放在Assets根目录下,有且仅有一个)
  • WebPlayerTemplates–存放网页模板,其中每个模板均以一个自定义名称的子目录形式存放。如果WebPlayerTemplates文件夹在Assets根目录下,那么它里面的所有脚本都不会被编译。否则,WebPlayerTemplates文件夹存在于其它子目录中,那它里面的所有脚本都会被编译。
  • StreamingAssets–存放媒体文件,如视频等。该文件夹下的资源会打包进发布包,它和Resources文件夹的区别是:Resources文件夹下的资源在打包时会被压缩,但StreamingAssets文件夹下的资源不会被压缩,而是原封不动的打包进发布包中。另外需要注意:此路径在PC/Mac电脑上可对文件实施“增删改查”等操作(在Windows8系统上进行增删等写入操作时报路径不允许访问),但在移动端只支持读取操作。(只能放在Assets根目录下,有且仅有一个)

脚本编译顺序

阶段 1:Standard AssetsPro Standard AssetsPlugins 文件夹里的运行时脚本被编译。

阶段 2:Standard Assets/EditorPro Standard Assets/EditorPlugins/Editor 文件夹里的编辑器脚本被编译。

阶段 3:所有不在Editor文件夹中的脚本被编译。

阶段 4:剩下的脚本(即在Editor文件夹中的脚本)被编译。

携程的返回值

使用协程要引入的命名空间:using System.Collections;

yield return null; // 下一帧再执行后续代码
yield return 6;//(任意数字) 下一帧再执行后续代码
yield break; //直接结束该协程的后续操作
yield return asyncOperation;//等异步操作结束后再执行后续代码
yield return StartCoroution(/*某个协程*/);//等待某个协程执行完毕后再执行后续代码
yield return WWW();//等待WWW操作完成后再执行后续代码
yield return new WaitForEndOfFrame();//等待帧结束,等待直到所有的摄像机和GUI被渲染完成后,在该帧显示在屏幕之前执行
yield return new WaitForSeconds(0.3f);//等待0.3秒,一段指定的时间延迟之后继续执行,在所有的Update函数完成调用的那一帧之后(这里的时间会受到Time.timeScale的影响);
yield return new WaitForSecondsRealtime(0.3f);//等待0.3秒,一段指定的时间延迟之后继续执行,在所有的Update函数完成调用的那一帧之后(这里的时间不受到Time.timeScale的影响);
yield return WaitForFixedUpdate();//等待下一次FixedUpdate开始时再执行后续代码
yield return new WaitUntil()//将协同执行直到 当输入的参数(或者委托)为true的时候....如:yield return new WaitUntil(() => frame >= 10);
yield return new WaitWhile()//将协同执行直到 当输入的参数(或者委托)为false的时候.... 如:yield return new WaitWhile(() => frame < 10);

Unity事件中心实现

EventManager事件管理类
EventType事件枚举类

EventManager

using System.Collections.Generic;
using ProjectBase.SingleBase;
using UnityEngine.Events;

namespace ProjectBase.EventCenter
{
    /// <summary>
    /// 事件中心
    /// </summary>
    public class EventManager : Single<EventManager>
    {
      
        private Dictionary<EventType, IEventData> eventDicts = new Dictionary<EventType, IEventData>();

        private interface IEventData
        {
        }

        private class EventData<T> : IEventData
        {
            public UnityAction<T> action;

            public EventData(UnityAction<T> action)
            {
                this.action += action;
            }
        }


        /// <summary>
        /// 注册事件
        /// </summary>
        /// <param name="type"></param>
        /// <param name="action"></param>
        public void AddEventListener<T>(EventType type, UnityAction<T> action)
        {
            if (eventDicts.ContainsKey(type))
            {
                EventData<T> eventData = (EventData<T>) eventDicts[type];
                eventData.action += action;
            }
            else
            {
                eventDicts.Add(type, new EventData<T>(action));
            }
        }

        /// <summary>
        /// 删除事件
        /// </summary>
        /// <param name="type"></param>
        /// <param name="action"></param>
        public void RemoveEventListener<T>(EventType type, UnityAction<T> action)
        {
            if (eventDicts.ContainsKey(type))
            {
                EventData<T> eventData = (EventData<T>) eventDicts[type];
                eventData.action -= action;
            }
        }

        /// <summary>
        /// 分发事件
        /// </summary>
        /// <param name="type"></param>
        /// <param name="info"></param>
        public void EventTrigger<T>(EventType type, T info)
        {
            if (eventDicts.ContainsKey(type))
            {
                EventData<T> eventData = (EventData<T>) eventDicts[type];
                eventData.action.Invoke(info);
            }
        }

        /// <summary>
        /// 清理所有事件
        /// </summary>
        public void Clear()
        {
            eventDicts.Clear();
        }
    }
}

EventType

namespace  ProjectBase.EventCenter
{
    public enum EventType
    {
        Dead,
        SceneLoadProgress
    }
}

示例: 使用事件中心 封装场景加载类

using System.Collections;
using ProjectBase.EventCenter;
using ProjectBase.SingleBase;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using AsyncOperation = UnityEngine.AsyncOperation;
using EventType = ProjectBase.EventCenter.EventType;

namespace ProjectBase.Scene
{
    public class SceneMgr : Single<SceneMgr>
    {
        
        /// <summary>
        /// 同步加载场景
        /// </summary>
        /// <param name="name"></param>
        public void LoadScene(string name)
        {
            SceneManager.LoadScene(name);
        }


        /// <summary>
        /// 异步加载场景
        /// </summary>
        /// <param name="name"></param>
        /// <param name="callBack"></param>
        public void LoadSceneAsync(string name, UnityAction callBack = null)
        {
            MonoManager.Instance.StartCoroutine(LoadSceneByAsync(name, callBack));
        }

        private IEnumerator LoadSceneByAsync(string name, UnityAction callBack)
        {
            AsyncOperation ap = SceneManager.LoadSceneAsync(name);
            while (!ap.isDone)
            {
                EventManager.Instance.EventTrigger(EventType.SceneLoadProgress, ap.progress);
                yield return 1; //下一帧继续
            }
            callBack?.Invoke();
        }
    }
}

观察者模式(Observer Pattern):

定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新