Unity2019最新科技VFX初探

Unity2019最新VFX技术研究

前言

最近这段时间都没有时间写博客,一方面是因为ECS项目遇到了瓶颈,实在无法忍受太多不完善的地方,又或者我对ECS的理解有误。但是最基本的游戏组件都无法使用,实在是让我开发起来十分吃力,尽管已经把地图开发进度强行推进到了9/30,但是离我的目标:无限地图,还有非常遥远的距离。期间花了一个星期来研究Shader Graph,又花一天时间来研究Visual Effect,中秋节又收到朋友邵伟的大作《Unity2017虚拟现实开发标准教程》,这里特别推荐一下:
书面
目录1
目录2
彩页
这本书内容非常详实,全是顶级干货,太赞了!而且还是官方授权认可的教材,对虚拟现实技术感兴趣的值得入手,希望此作大卖,VR技术革命更快到来!
ECS的项目需要暂停一段时间,因为要等待官方更新更多的组件,例如物理/动画/渲染之类的基本组件,混合开发是可行的,但是加大了开发的复杂度,还不如直接用OOP。所以还是要等待纯粹的ECS开发到来,进行Pure ECS开发。
当然一些大神已经在使用Pure ECS开发游戏了,但是我的技术实在有限,很多地方困难重重,以至于无法有效解决。
所以这一篇不再写ECS,而是研究VFX(Visual Effect,可视化特效)。

准备工作

这里使用的学习案例是官方的SpaceShip示例,源码下载地址,特别要注意的是:需要安装Git LFS
0下载Unity编辑器(2019.2.0f1 or 更新的版本),if(已经下载了)continue;
1克隆:git clone [email protected]:Unity-Technologies/SpaceshipDemo.git --recurse下载发布包
2注意:不要下载Github的Zip包,下载发布包。然后将Spaceship Demo添加到Unity Hub项目中;
3用Unity Hub打开的开源项目:Spaceship Demo,等待Unity进行编译工作;
4打开项目后,启动场景在Scenes目录下,打开Boot场景。
之所以不下载Zip包,而是发布页面的压缩包,是因为Github不能生成正确的LFS存档,所以点击发布包的链接下载。

启动

先启动整个示例看一下炫酷的特效场景好了,哇噢,哇噻,炫酷的粒子特效,屌炸天的全息控制台……
总之,这个项目的起点是AAA级游戏。
其实网络上早就有这个项目的演示视频了,我也想录制一个,不过,我还是喜欢文字来记录一下,这样比较节约读者的宽带。
实际上是比较懒的缘故,就没有录制视频,还是大家自己运行一下比较爽。
那么这个项目的启动场景里面其实没有几个脚本,看了一遍以后发现根本不明白他的运行机制是什么,直到我们注意到Gameplay Ingredients框架,这是大佬peeweek写的游戏开发框架,有兴趣的朋友可以去研究一下。
就是这个东西在作怪,有些脚本,即使我们不去调用它,脚本也会自动运行。
Boot场景
这些对象大家可以挨着看一遍,根本没有调用切换场景之类的方法,但是所有的东西都自动运行。

Initializing all Managers…
UnityEngine.Debug:Log(Object)
GameplayIngredients.Manager:AutoCreateAll() (at LocalPackages/net.peeweek.gameplay-ingredients/Runtime/Managers/Manager.cs:37)

我们发现Debug中有这样一行日志,于是点进去:

[RuntimeInitializeOnLoadMethod]
static void AutoCreateAll()
{
    
    
    var exclusionList = GameplayIngredientsSettings.currentSettings.excludedeManagers;

    Debug.Log("Initializing all Managers...");
    foreach(var type in kAllManagerTypes)
    {
    
    
        if(exclusionList != null && exclusionList.ToList().Contains(type.Name))
        {
    
    
            Debug.Log($"Manager : {type.Name} is in GameplayIngredientSettings.excludedeManagers List: ignoring Creation");
            continue;
        }
        var attrib = type.GetCustomAttribute<ManagerDefaultPrefabAttribute>(); 
        GameObject gameObject;

        if(attrib != null)
        {
    
    
            var prefab = Resources.Load<GameObject>(attrib.prefab);

            if(prefab == null) // Try loading the "Default_" prefixed version of the prefab
            {
    
    
                prefab = Resources.Load<GameObject>("Default_"+attrib.prefab);
            }

            if(prefab != null)
            {
    
    
                gameObject = GameObject.Instantiate(prefab);
            }
            else
            {
    
    
                Debug.LogError($"Could not instantiate default prefab for {type.ToString()} : No prefab '{attrib.prefab}' found in resources folders. Ignoring...");
                continue;
            }
        }
        else
        {
    
    
            gameObject = new GameObject();
            gameObject.AddComponent(type);
        }
        gameObject.name = type.Name;
        GameObject.DontDestroyOnLoad(gameObject);
        var comp = (Manager)gameObject.GetComponent(type);
        s_Managers.Add(type,comp);

        Debug.Log(string.Format(" -> <{0}> OK", type.Name));
    }
}

原来在Manager脚本中自动调用了这个方法,我们注意到这个静态方法使用了[RuntimeInitializeOnLoadMethod]定语标签来修饰,我们想猫腻就在这里,于是我写了一个方法来进行测试:

  [RuntimeInitializeOnLoadMethod]
  static void AutoCall()
  {
    
    
      Debug.Log("[RuntimeInitializeOnLoadMethod]...呃,这个定语标签起作用了,自动调用了所修饰的静态方法!!!");
  }

编译后运行,果然得到了对应的日志:

[RuntimeInitializeOnLoadMethod]…呃,这个定语标签起作用了,自动调用了所修饰的静态方法!!!
UnityEngine.Debug:Log(Object)
GameplayIngredients.Manager:AutoCall() (at LocalPackages/net.peeweek.gameplay-ingredients/Runtime/Managers/Manager.cs:29)

要知道,我们根本没有调用这个方法,而是这个静态方法被系统自动调用了,这就是Gameplay Ingredients框架的魔力了。
可惜作者并没有说详细的使用方法,于是我们只能摸着石头过河了。
既然已经打开了Manager脚本,就从这里开始研究好了,看了一会儿,我们就知道Manager脚本是所有Manager类的抽象基类,典型的OOP设计模式。在Manager抽象类中定义了一个字典来对所有的Manager进行管理:

private static Dictionary<Type, Manager> s_Managers = new Dictionary<Type, Manager>();

所有的游戏管理器都被保存在这个字典里面,在需要的时候可以通过公开的Get方法来获取:

 public static T Get<T>() where T: Manager
 {
    
    
     if(s_Managers.ContainsKey(typeof(T)))
         return (T)s_Managers[typeof(T)];
     else
     {
    
    
         Debug.LogError($"Manager of type '{typeof(T)}' could not be accessed. Check the excludedManagers list in your GameplayIngredientsSettings configuration file.");
         return null;
     }
 }

这里使用了泛型T来代表继承了Manager的管理器子类,只需要从字典里返回出去即可,方便快捷,也是OOP常用的方式。
接下来是用一个静态只读的字段来保存所有管理器的类型:

static readonly Type[] kAllManagerTypes = GetAllManagerTypes();

它又调用了:

static Type[] GetAllManagerTypes()
 {
    
    
     List<Type> types = new List<Type>();
     foreach(var assembly in AppDomain.CurrentDomain.GetAssemblies())
     {
    
    
         Type[] assemblyTypes = null;

         try
         {
    
    
             assemblyTypes = assembly.GetTypes();
         }
         catch
         {
    
    
             Debug.LogError($"Could not load types from assembly : {assembly.FullName}");
         }

         if(assemblyTypes != null)
         {
    
    
             foreach (Type t in assemblyTypes)
             {
    
    
                 if (typeof(Manager).IsAssignableFrom(t) && !t.IsAbstract)
                 {
    
    
                     types.Add(t);
                 }
             }
         }

     }
     return types.ToArray();
 }

这里的管理器类型是写在程序集里面的,源码已开源,有兴趣的朋友可以去看看:
Gameplay Ingredients
这里就不去深究了,完全是个无底洞!
但是通过Debug日志,我们发现它加载了以下的管理器:

. AudioManager 音频管理器
- SubtitleManager 字幕管理器
- DebugPOVManager 调试第一人称视点管理器
- VFXDebugManager 可视化特效调试管理器
- SettingManager 设置管理器
- ShakeManager 震动管理器
- FullScreenFadeManager 全屏淡入管理器
- GameManager 游戏管理器
- GameSaveManager 游戏保存管理器
- UIEventManager 用户界面事件管理器
- VirtualCameraManager 虚拟相机管理器
- LevelStreamingManager 关卡加载管理器

总共有12个管理器,这些管理器会优先在Resources资源文件夹加载预设,如果没有预设就用脚本生成,相关代码如下:

[RuntimeInitializeOnLoadMethod]
  static void AutoCreateAll()
  {
    
    
      var exclusionList = GameplayIngredientsSettings.currentSettings.excludedeManagers;

      Debug.Log("Initializing all Managers...");
      foreach(var type in kAllManagerTypes)
      {
    
    
          if(exclusionList != null && exclusionList.ToList().Contains(type.Name))
          {
    
    
              Debug.Log($"Manager : {type.Name} is in GameplayIngredientSettings.excludedeManagers List: ignoring Creation");
              continue;
          }
          var attrib = type.GetCustomAttribute<ManagerDefaultPrefabAttribute>(); 
          GameObject gameObject;

          if(attrib != null)
          {
    
    
              var prefab = Resources.Load<GameObject>(attrib.prefab);

              if(prefab == null) // Try loading the "Default_" prefixed version of the prefab
              {
    
    
                  prefab = Resources.Load<GameObject>("Default_"+attrib.prefab);
              }

              if(prefab != null)
              {
    
    
                  gameObject = GameObject.Instantiate(prefab);
              }
              else
              {
    
    
                  Debug.LogError($"Could not instantiate default prefab for {type.ToString()} : No prefab '{attrib.prefab}' found in resources folders. Ignoring...");
                  continue;
              }
          }
          else
          {
    
    
              gameObject = new GameObject();
              gameObject.AddComponent(type);
          }
          gameObject.name = type.Name;
          GameObject.DontDestroyOnLoad(gameObject);
          var comp = (Manager)gameObject.GetComponent(type);
          s_Managers.Add(type,comp);

          Debug.Log(string.Format(" -> <{0}> OK", type.Name));
      }
  }

貌似这段代码一开始引用过了,就当是凑字数好了,可惜没有字数要求,所以算是白凑字数了Orz。
加载完成就会打印日志:-> 某某管理器 OK

今天就先研究到这里好了,洗洗睡了!如果有朋友对这个感兴趣的话请留言,这样也许会继续下一篇,否则就偷懒不写了。

作者的话

Alt

如果喜欢可以点赞支持一下,谢谢鼓励!如果有什么疑问可以给我留言,有错漏的地方请批评指证!
技术难题?加入开发者联盟:566189328(QQ付费群)提供有限技术探讨,以及,心灵鸡汤Orz!
当然,不需要技术探讨也欢迎加入进来,在这里劈柴、遛狗、聊天、撸猫!( ̄┰ ̄*)

猜你喜欢

转载自blog.csdn.net/qq_30137245/article/details/100938951