关于AI逻辑写在Lua中的问题

1)关于AI逻辑写在Lua中的问题
​2)Shader中宏的作用
3)Update中的new Struct对象
4)通过在编辑的预制体中获取资源路径


这是第266篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题,建议阅读时间10分钟,认真读完必有收获。

UWA 问答社区:answer.uwa4d.com
UWA QQ群2:793972859(原群已满员)

Lua

Q:现在战斗的核心逻辑都在xLua里面,包含了AI的逻辑,这部分计算量极大,会造成画面卡死。

尝试过用Thread把xLua虚拟机包一层,结果还是会报Main Thread的错误。也尝试用闭包的方式,从Lua回传到CSharp,用System.Action接住,然后丢线程里面跑,但是会卡断主线程。

现在想尝试用两个Lua虚拟机,一个放Thread里面只跑战斗,其他的放在主Lua虚拟机中。那么整个游戏运行起来,会有3个虚拟机:Mono、xLua和xLuaInThread。

策划希望AI做得聪明一些,对应的思考维度和运算量都会大幅提高。不知道是否有其他推荐的方案?

A1:我觉得不是虚拟机方向的问题:

  1. Lua和Unity交互的细节没有把控好,比如反复在lua.transform.position等等类似的。

  2. 不管是Tolua还是xLua,并不是说完全就不用C#了。应该是基于几个原则:扩展性、热更性和性能,比如MMORPG的头顶UI肯定不是全部是Lua开发的,业务在Lua但是移动那些肯定是C#(一旦做完了,接下来几乎不会再动到C#代码了)。

  3. Lua和C#穿插的细节需要把控。

  4. Lua本身的使用细节也需要把控好。

感谢沈杰@UWA问答社区提供了回答

A2:不需要多个虚拟机。可以阅读《游戏编程精粹》里的这两篇文章(年代有点久了不记得是哪一本,也不记得具体讲了那些内容,只是获得了思路后,自己实现过):

  1. 用于游戏对象AI的微线程
  2. 使用微线程管理AI

我在曾经的项目中实现基于协程的任务管理器,大致有这些特点:

  1. 每个游戏对象运行的脚本在独立创建的协程上。
  2. 每个协程入口统一采用类似C语言的入口方法规范,如:
  • 入口函数Main。
  • 随眠函数Sleep,Yield,Waitframe,Waittime(这些方法可以精确控制AI的计算频率与精度)。
  • 退出相关方法Exit,Atexit。
  1. 统一在宿主程序中,对游戏中多个协程进行按需调度,比如等待指定时间,帧数后调用Resume唤醒协程。

感谢lujian@UWA问答社区提供了回答


Shader

Q:Particle Additive Shader中使用了该宏:UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);,查了一下没找到相关说明。字面理解是声明了一个深度图。

在默认管线(向前渲染)中,按照常规理解深度图需要通过设置Camera的depthTextureMode来指定。还请各位指教。

A:Shader中很多宏的确是没有官方说明的,我们需要去CGInclude文件夹中寻找其确切定义。

UNITY_DECLARE_DEPTH_TEXTURE在HLSLSupport.cginc里:

 

的确是为了声明一张图,回到Particle Add.shader中发现是为了声明深度图:

 

这是为了在片元着色器中的深度采样做准备:

 

该SAMPLE_DEPTH_TEXTURE_PROJ方法同样在HLSLSupport.cginc中:

 

然后继续在该文件中找tex2Dproj:

 

由此可见这些操作就是在相机保存的深度图中,通过tex2D获取了深度,为LinearEyeDepth准备参数从而得到场景深度。该Shader通常在开了软粒子之后被用到。

感谢翟孟飞@UWA问答社区提供了回答


Script

Q:Struct中包含String字段,如果在Update中new,会不会有GC问题?考虑是否需要用缓存池来解决,目前我测试了一下将Struct改成Class是肯定有,如果是Struct类型,就没找到。

A1:对Class进行new是会分配堆内存,但是这个堆内存不一定是String字段造成的。Class的实例本身就会占用堆内存,而它的字段会不会造成堆内存分配,取决于new的时候会不会对类型进行实例化。实例化的时候给String赋值,如果是直接确定的字符串内容,编译器会自动进行缓存优化,只会在第一帧创建字符串分配内存。

至于Struct,结构体作为值类型本身不会占用堆内存。对Struct进行new会不会分配堆内存,取决于new的时候,它的字段会不会实例化,会不会占用堆内存。如果Struct没有写构造函数,即使里面有引用类型,在new Struct的时候也不会实例化,不会分配堆内存。如果写了构造函数,就要看构造函数里面是否分配堆内存了。String的情况与上文提到的一致。

感谢Prin@UWA问答社区提供了回答

A2:C#语法中除了构造函数造成的堆内存分配,还有结构体对外传递造成栈上逃逸问题。具体表现是构造的结构体作用域不仅局限在Update函数内。

在Unity的Playable模块里可以见到大量的ref Struct签名,是C# 7里专门解决栈逃逸的语法,依葫芦画瓢即可。

感谢陈xx@UWA问答社区提供了回答


Script

Q:我想调用AssetDatabase.GetAssetPath 获取资源路径,但我点击图1物体,使用AssetDatabase.GetAssetPath(gameObject),却获取不到。想请问:如何通过图1获取到图2对应的物体路径?

 

A1:PrefabUtility.GetNearestPrefabInstanceRoot用来获取最近的预制体实例;Root PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot用来获取预制体路径。

        [MenuItem("Tools/GetPrefabInstancePath")]
        public static void GetPrefabInstancePath()
        {
            if (null != Selection.activeGameObject)
            {
                if (PrefabUtility.IsPartOfPrefabInstance(Selection.activeGameObject))
                {
                    var path = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(Selection.activeGameObject);
                    Debug.Log("Prefab Path:" + path);
                }
            }
        }

  

感谢马三小伙儿@UWA问答社区提供了回答

A2:赞同楼上的回答,此处我再稍作补充:

PrefabUtility.GetNearestPrefabInstanceRoot和AssetDatabase.GetAssetPath,前者针对的是已经实例出来的对象(官方:返回指定对象所属的最近预制件实例根的资源路径),后者针对的是资源(官方:返回相对于项目文件夹的资源路径名称)。题主的问题属于是前者,如果直接点击资源并用AssetDatabase.GetAssetPath,就可以获取到资源路径。

感谢翟孟飞@UWA问答社区提供了回答

 

 

20210906
更多精彩问题等你回答~

1.Vulkan API的性能及兼容性
2.Unity TMP字体方案如何选择
3.如何实现AAB包的增量更新

 

封面图来源于:Daydream Renderer for Unity
Daydream Renderer是一组脚本和着色器,旨在允许在Daydream平台上以60fps的帧率实现高质量实时渲染。


 

今天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题也许都只是冰山一角,我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入,也许你的方法恰能解别人的燃眉之急;而他山之“石”,也能攻你之“玉”。

 

官网:www.uwa4d.com
官方技术博客:blog.uwa4d.com
官方问答社区:answer.uwa4d.com
UWA学堂:edu.uwa4d.com
官方技术QQ群:793972859(原群已满员)

posted @ 2021-09-13 11:54  UWATech  阅读(111)  评论(0编辑  收藏  举报