Unity的协程的原理理解
协程最近在看面试题,经常会遇到一个经典的八股问题:协程是多线程吗?底层原理是什么。虽然接触Unity好几年了,但是一直没用探究过其中的底层原理,看到这个问题也大脑空白。今天就来探寻一下其中的原理。 测试代码废话不多说,直接上代码。代码非常简单一个TestCoroutine函数内yield return null和log输出。 IL代码C#的代码看不出什么,打开IL代码。可以清晰编译器为我们的协程生成了一个类名叫d__1,并且继承了IEnumerator,类中除了IEnumerator的Current、MoveNext()和Reset(),还额外多了一个state变量。我们自己写的TestCoroutine函数中就new了这个类的实例。其中类中最关键的就是这个state变量的使用。 state变量的使用从截图可以看到,MoveNext()中维护的switch充当了一个类似状态机的逻辑,每个yield return null就变成了一个case,state变量就变成了case块的记录,通过枚举去执行不同的逻辑,这就是协程可重入机制的根本原因。 协程的性能消耗既然协程是new的了一个类...
C#匿名函数与闭包
起因最近看到了一段C#程序,一开始就简单的以为只是一个for循环的0~9的打印,但是深入了解之后发现我错了,而且对其中的原理相知甚少。代码如下: 12345678910111213public static void Main(string[] args){ Action[] actions = new Action[10]; for (int i = 0; i < 10; i++) { actions[i] = () => {Console.WriteLine(i.ToString()); }; } foreach (var action in actions) { action(); }} 这段代码的运行结果输出是10个10,如果做对了,想必已经是了解其中的原理,下面的内容可以不用看了。 原因原因其实很简单,使用编辑器查看IL代码,可以很清晰的看到:编译器为匿名函数生成了一个名为<>c__DisplayC...
Unity的==与空并运算符(?.)
Unity.Object使用==与?.的区别众所周知,C#的判空有两种:==与?.(空并运算符),之前因为没有深入了解两种判空的在Uniyt.Object中的差别,开发的时候更是想到谁就用谁,造成了一些Crash,下面详细讲解两者的区别。 Unity.Object使用?.如上图代码和结果所示,在使用GameObject.DestroyImmediate函数当帧立即销毁之后,打印name还是执行了并且报空,说明?.无法正确的判断Unity.Object为空。 Unity.Object使用==如上图代码和结果所示,在使用==判断时,成功判断了对象为空。 差别的原因在解释之前,补充一段关于Unity.Object的知识。Unity运行时是C++的,所有的对象都在C++层管理,在C#层只是有一个引用对象指向C++对象。所以,就有一种情况,就是C++层的对象被销毁了,但是C#层的对象还存在,导致判空出错。了解了这个知识点,下面开始探究问题所在。以下是Unity重载==运算符的源码:上面的方法...
算法--判断点是否在多边形中(包含凹凸多边形)
判断点是否在一个凸多边形内部,可以根据面积、叉乘的方法判断。但是包括凹多边形的时候,就得使用射线法判断。下面介绍这种算法。 算法原理奇-偶规则(Odd-even Rule):奇数表示在多边形内,偶数表示在多边形外从任意位置p作任意方向的一条射线,若与该射线相交的多边形边的数目为奇数,则p是多边形内部点,否则是外部点。以上图为例:从红点向任意方向发射射线(上图是向左和向右),与图形的边的交点总和为奇数时,点在内部;为偶数时,点在外部。(上图向左有5个点,向右有3个点),所以红点在多边形的内部。所以,问题就从判断点在多边形的内部转化为了:判断点朝任意方向的射线与多边形的边的交点个数的奇、偶问题。 如何判断射线与多边形的交点?从上图可以非常直观地看出,取出多边形中两个相邻的点P1、P2,根据两点得到直线的方程为:Y=(y1-y2)/(X1-X2)*(X - X1)+Y1。将待监测点P0的X0代入直线方程(如果向上打射线),计算出X0在该射线上的Y位置,如果Y>Y0则说明:该P0点在线段P1、P2的下方,交点数+1;反之则为下方。 代码实现12345678910...
Unity节点编辑器(六) -- 引导节点与引导流程
引导流程需求目前已经有了节点和引导的遮罩,为了实现编辑步骤,还要把每一步需要高亮的位置或者需要高亮跟随的GameObject放入节点数据中,并根据节点的先后顺序逐一运行逻辑。所以就需要有3个事情:1.引导节点。目前节点编辑器只有开始节点,无法存储引导节点的数据,所以需要继承于NodeBase和数据,实现编辑器的可视化功能。2.引导节点逻辑处理脚本。为了之后的拓展性,所以针对每一种引导节点都有一个特定的处理脚本。通过维护一个状态机的生命周期,在脚本中拿到节点数据进行具体的逻辑处理。3.引导系统。我把这个写成了单例。负责加载引导数据并反序列化出来,同时也要提供根据引导名字开启引导的接口。 引导节点实现引导数据数据类继承于原来的,引导需要知道高亮指定的GameObject,数据就需要知道GameObject的ID(这里通过给对应的物体挂载脚本),引导数据传入ID。 12345678910[Serializable]public class GuideData:NodeSerializationData{ [SerializeField] public string...
Unity节点编辑器(五) -- 引导遮罩
遮罩需求目前市面上游戏的引导,基本上都采用了在需要突出的地方高亮引导玩家,通过点击等方式去触发下一步操作。所以需求就抽象成两个:1.高亮某一块区域2.高亮区域的点击判断和通知 高亮区域实现高亮区域有多种做法,第一种是通过继承Unity的BaseMeshEffect去自己写顶点、三角面最后形成高亮,第二种是使用Shader通过像素的判定实现。第一种比较里面的逻辑比较复杂,所以我用的是第二种。 矩形高亮区域的C#代码实现一个矩形高亮区域需要两个数据:矩形的中心和大小。为了使用同一个材质实现合批,我使用一个继承于BaseMeshEffect的类中(Unity提供的类不熟悉的可以查一下),在类中把这两个数据写入每个顶点中。 顶点处理基类重写ModifyMesh方法,在其中使用抽象方法SetVertexData,传入顶点数据,并重新赋值出来。 123456789101112131415161718192021222324252627282930public abstract class MaskVertexBase : BaseMeshEffect{ public over...
Unity节点编辑器(四) -- 保存&打开Graph数据
打开SO数据使用Unity内置函数添加双击打开事件使用Unity提供的OnOpenAsset(0),然后重新设置graphView的数据并重绘。 123456789101112131415161718[OnOpenAsset(0)]public static bool OnOpen(int instanceID, int line){ GraphSoData nodeGraph = EditorUtility.InstanceIDToObject(instanceID) as GraphSoData; if (nodeGraph != null) { Open(nodeGraph); return true; } return false;}public static void Open(GraphSoData nodeGraph){ ShowExample(); m_graphView.SetGraphData(nodeGraph); m_graphVi...
Unity节点编辑器(三) -- 创建Node节点
编辑器数据类要做的节点如下图所示根据图片可以很清晰的知道,数据分为4部分。1.要有一个类存储着所有的节点数据。2.每个节点需要保存自己的类型名字(用于反射生成)、节点唯一ID(用于标识节点)、标题、节点位置、输出/输出端口的数据、以及一个Object类型的每个子类自己的数据。3.端口数据记录端口所在节点ID、端口唯一ID以及连接的数据。4.连接数据记录着连接的节点ID和端口。我的实现使用了Unity的ScriptableObject。数据结构的图如下。 GraphSoData 黑板数据12345678910111213141516[Serializable]public class GraphSoData : ScriptableObject{ [SerializeField] public string graphName; [SerializeField] public List<NodeSoData> nodeSoDataList = new List<NodeSoData>(); public void Dis...
Unity节点编辑器(二) -- GraphView窗口
编辑器窗口创建我这里直接用了Unity编辑器uss创建一个窗口。然后添加了一个网格样式。 窗口代码窗口中持有一个NodeGraphView实例,并且在其中OnEnable中进行初始化。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293public class NodeGraphWindow : EditorWindow{ protected static NodeGraphView m_graphView; [MenuItem("★Tools★/NodeGraphWindow")] public static void ShowExample() { GetWindow<NodeGraphWind...
Unity节点编辑器(一) -- 需求分析
最近需求的思考最近工作上在处理项目内的引导系统,我之前的做法是根据策划的需求,通过继承引导基类实现具体子类的做法,来实现不同的引导。但是这样有一个缺点:所有的引导实现都基于我的硬编码,当引导的流程变更的时候,我代码的流程也需要跟着修改,非常麻烦。而且这些变更也仅仅是步骤改变,1->2->3变成了1->3->2,修改就是纯粹的体力活,非常的琐碎。然后最近在各种机缘巧合之下,接触到了节点编辑器,突然激发了我的灵感,要是我把引导的逻辑封装在一个Node里面,由策划自己去建立Node流程,就是完全由策划编辑,我提供具体的引导Node实现,完美解决。 关于节点编辑器其实这个东西在已经应用非常广泛了,Unity的ShareGraph和UE的蓝图,都是节点编辑器的应用。而且在Unity上开源的节点编辑器框架也不少,比较出名的就有:xNode,NodeGraphProcessor。本来是打算直接使用这些框架,省下自己造轮子的时间,但是深入了解之后,发现这些框架为了通用性,代码都比较多且太重度了(我目前的需求根本用不上)。所以就想着自己造一个只提供流程的简单节点编辑器。 编...





