{"id":2481,"date":"2021-05-30T03:16:46","date_gmt":"2021-05-29T19:16:46","guid":{"rendered":"https:\/\/www.clonefactor.com\/wordpress\/?p=2481"},"modified":"2021-05-31T13:36:39","modified_gmt":"2021-05-31T05:36:39","slug":"%e5%af%a6%e4%bd%9c-triangular-field-of-view-by-boxoverlap-raycast","status":"publish","type":"post","link":"https:\/\/www.clonefactor.com\/wordpress\/public\/2481\/","title":{"rendered":"\u5be6\u4f5c\u4e09\u89d2\u8996\u7dda\u6e2c\u6575, Triangular field of view via BoxOverlap + Raycast"},"content":{"rendered":"\n<p>\u5f88\u4e45\u4ee5\u524d\u5beb\u904e\u4e00\u7bc7 <a href=\"https:\/\/www.clonefactor.com\/wordpress\/program\/c\/1729\/\" data-type=\"post\" data-id=\"1729\">\u4e8c\u5206\u6cd5, \u969c\u7919\u7269\u908a\u6cbf\u67e5\u627e &amp; \u8cc7\u6e90\u7ba1\u7406<\/a> \u7d50\u679c\u597d\u50cf\u4e0d\u5c0f\u5fc3\u8aa4\u5c0e\u4e86\u4e00\u4e9b\u7db2\u53cb\u4ee5\u70ba\u9019\u662f\u6b63\u5e38\u7684\u505a\u6cd5&#8230;. <br>\u4e0d\u6703\u5587~~ \u8ddf\u8457\u505a\u4e00\u5b9a\u88ab\u4e3b\u7ba1\u6253\u69cd, \u592a\u6d6a\u8cbb\u8cc7\u6e90\u54af.<br>\u6240\u4ee5\u6700\u8fd1\u6709\u6a5f\u6703\u5beb\u4e00\u4e0b Behavior tree \u7684\u5e95\u5c64\u4ee3\u78bc, \u9700\u8981\u91cd\u65b0\u6559 NPC \u8d70\u8def&#8230;<br>\u9806\u5e36\u9700\u8981\u5be6\u73fe\u4e09\u89d2\u8996\u7dda\u6e2c\u6575\u7684\u65b9\u5f0f,\u53c8\u8981\u4fdd\u6301\u6027\u80fd\u512a\u5316,\u4eca\u6b21\u5c31\u4f86\u5be6\u73fe\u4e00\u500b\u6bd4\u8f03\u7c21\u55ae\u7684\u5be6\u4f5c\u65b9\u5f0f.<br>\u69cb\u60f3\u5f88\u7c21\u55ae<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>\u8a02\u597d\u53ef\u8996\u7684\u6700\u9060\u8ddd\u96e2 &amp; \u89d2\u5ea6<\/li><li>\u5148\u7528 OverlapBox \u628a\u7bc4\u570d\u5167\u53ef\u4ee5\u770b\u5230\u7684\u6771\u897f\u7d71\u7d71\u8a18\u4e0b\u4f86<\/li><li>\u518d for..loop \u9010\u500b\u6aa2\u67e5\u662f\u4e0d\u662f\u80fd\u7531NPC \u773c\u775b\u76f4\u63a5 &#8220;\u770b\u5230&#8221;\u5c0d\u65b9<\/li><li>\u628a\u7d50\u679c\u66ab\u5b58\u4e0b\u4f86\u7d66\u5176\u4ed6\u7a0b\u5e8f\u8a2a\u554f.<\/li><\/ul>\n\n\n\n<p>\u5316\u6210\u8981\u6c42\u5927\u6982\u662f\u9019\u6a23.<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>\u5148\u7528 <a rel=\"noreferrer noopener\" href=\"https:\/\/docs.unity3d.com\/ScriptReference\/Physics.OverlapBoxNonAlloc.html\" target=\"_blank\">OverlapBoxNonAlloc <\/a>\u4f86\u6aa2\u5b9a\u7bc4\u570d\u4f86\u7684\u76ee\u6a19<\/li><li>\u4f7f\u7528 Overlap \u7684\u56de\u50b3\u7d50\u679c\u5c0d\u76ee\u6a19\u9032\u884c\u7be9\u9078, \u4e26\u4f7f\u7528 <a rel=\"noreferrer noopener\" href=\"https:\/\/docs.unity3d.com\/ScriptReference\/Physics.Raycast.html\" data-type=\"URL\" target=\"_blank\">Raycast<\/a>\u5254\u9664\u89d2\u5ea6\u53ca\u8ddd\u96e2\u5916\u7684\u76ee\u6a19, \u53ea\u7559\u4e0b\u9069\u5408\u7684\u76ee\u6a19.<\/li><li>\u53ea\u5728 FixedUpdate \u4e2d\u7b97\u6642\u9593, \u57f7\u884c\u9593\u9694\u512a\u5316 (to \u521d\u5fc3, \u56e0\u70ba\u6211\u5011\u4e0d\u4ecb\u610f\u7269\u4ef6\u5728 &lt; 1 \u79d2\u7684\u9593\u9694\u4e4b\u9593\u662f\u5426\u9032\u5165\u8996\u89d2\u7bc4\u570d,\u5225\u6d6a\u8cbb\u8cc7\u6e90)<\/li><li>\u984d\u5916 : \u5229\u7528\u4e09\u89d2\u5b9a\u7406\u81ea\u52d5\u7b97\u51fa Overlap Box \u7684\u4f4d\u7f6e (\u70ba\u4e86<s>\u8eb2\u61f6<\/s>\u66f4\u76f4\u89c0\u7684\u7528\u8ddd\u96e2+\u89d2\u5ea6\u4f86\u8a2d\u5b9a)<\/li><\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">\u5be6\u4f5c\u5fc3\u5f97 &#8211; \u57f7\u884c\u9593\u9694\u512a\u5316<\/h3>\n\n\n\n<p>\u4e00\u500b\u5e38\u7528\u7684\u5c0f\u6280\u5de7\u5c0d\u4e00\u4e9b\u7121\u9700\u6bcf\u79d2\u57f7\u884c 60\u6b21\u7684\u4ee3\u78bc\u9032\u884c\u5b9a\u671f\u66f4\u65b0\u7684\u5beb\u6cd5 periodic update. \u597d\u50cf\u6c92\u6709\u5beb\u904e\u6587\u5c31\u9806\u4fbf\u9019\u88e1\u5beb\u5beb.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/ \u5e0c\u671b\u66f4\u65b0\u7684\u9593\u9694(\u53ef\u8abf)\npublic float m_Interval = 0.5f; \n\n\n\/\/ \u9632\u6b62\u540c\u4e00\u5e40\u5167\u57f7\u884c\u591a\u6b21 FixedUpdate\nprivate int m_FrameCount = 0;\n\/\/ \u7d00\u9304\u4e0a\u6b21\u57f7\u884c\u6642\u9593\nprivate float m_LastPeepTime = 0f;\n\nprivate void FixedUpdate()\n{\n\tif (m_FrameCount != Time.frameCount &amp;&amp;\n\t\tTime.timeSinceLevelLoad - m_LastPeepTime >= m_Interval)\n\t{\n\t\tm_LastPeepTime = Time.timeSinceLevelLoad;\n\t\tm_FrameCount = Time.frameCount;\n\t\t\n\t\tTakeALook(); \/\/ \u5e0c\u671b\u9032\u884c\u7684\u52d5\u4f5c.\n\t}\n}<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\u5be6\u4f5c\u5fc3\u5f97 &#8211; \u8996\u89d2\u7bc4\u570d\u6aa2\u6e2c<\/h3>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"alignright size-medium is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/sin_cos_tan-300x225.jpg\" alt=\"\" class=\"wp-image-2512\" width=\"150\" height=\"113\" srcset=\"https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/sin_cos_tan-300x225.jpg 300w, https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/sin_cos_tan-1024x768.jpg 1024w, https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/sin_cos_tan-768x576.jpg 768w, https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/sin_cos_tan-1536x1152.jpg 1536w, https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/sin_cos_tan-2048x1536.jpg 2048w, https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/sin_cos_tan-359x269.jpg 359w\" sizes=\"auto, (max-width: 150px) 100vw, 150px\" \/><\/figure><\/div>\n\n\n\n<p>\u5728\u88fd\u4f5c\u4e0a\u7531\u65bc\u5e0c\u671b\u81ea\u52d5\u7b97\u51fa OverlapBox \u7684\u6700\u5c0f\u7bc4\u570d\u6211\u4f7f\u7528\u4ee5\u4e0b\u7684\u8fa6\u6cd5.<br>1) \u67e5\u627e Box \u7684\u5782\u76f4\u4e2d\u5fc3, \u7531\u773c\u6674\u5230\u6700\u9060\u8ddd\u96e2, \u7531\u65bc Vector3.Lerp \u7684\u7279\u6027\u6211\u5011\u76f4\u63a5\u4ee5 0.5f \u5c31\u53ef\u4ee5\u627e\u53d6\u5782\u76f4\u7684\u8ddd\u96e2<br>2) \u67e5\u627e Box \u7684\u6a6b\u5411\u8ddd\u96e2, \u4ee5\u4eba\u7269\u7684\u773c\u6674\u4f5c\u70ba\u5713\u5fc3, \u6700\u9060\u8996\u8ddd\u7576\u534a\u5f91, \u53ef\u8996\u89d2\u5ea6\u5247\u4ee5\u4e09\u89d2\u51fd\u6578\u7528\u67e5\u627e\u5c0d\u908a, \u5f9e\u800c\u5f97\u77e5\u6a6b\u5411\u95ca\u5ea6<br>3) \u81f3\u6b64 Overlap Box \u7684\u4e2d\u5fc3, \u9577\u5bec, \u65cb\u8f49\u89d2 \u8cc7\u8a0a\u9f4a\u5099.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"887\" height=\"591\" src=\"https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/ViewAngleStressTest50_02.png\" alt=\"\" class=\"wp-image-2491\" srcset=\"https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/ViewAngleStressTest50_02.png 887w, https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/ViewAngleStressTest50_02-300x200.png 300w, https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/ViewAngleStressTest50_02-768x512.png 768w, https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/ViewAngleStressTest50_02-359x239.png 359w\" sizes=\"auto, (max-width: 887px) 100vw, 887px\" \/><figcaption>\u524d\u5411 180 \u5ea6\u7684\u8a08\u7b97<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-gallery columns-1 is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex\"><ul class=\"blocks-gallery-grid\"><li class=\"blocks-gallery-item\"><figure><img loading=\"lazy\" decoding=\"async\" width=\"963\" height=\"698\" src=\"https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/ViewAngleStressTest50_03.png\" alt=\"\" data-id=\"2494\" data-full-url=\"https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/ViewAngleStressTest50_03.png\" data-link=\"https:\/\/www.clonefactor.com\/wordpress\/public\/2481\/attachment\/viewanglestresstest50_03\/\" class=\"wp-image-2494\" srcset=\"https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/ViewAngleStressTest50_03.png 963w, https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/ViewAngleStressTest50_03-300x217.png 300w, https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/ViewAngleStressTest50_03-768x557.png 768w, https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/ViewAngleStressTest50_03-359x260.png 359w, https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/ViewAngleStressTest50_03-620x450.png 620w\" sizes=\"auto, (max-width: 963px) 100vw, 963px\" \/><\/figure><\/li><\/ul><figcaption class=\"blocks-gallery-caption\">\u5f8c\u65b9 180 \u5ea6\u7684\u8a08\u7b97<\/figcaption><\/figure>\n\n\n\n<p>4)\u7576\u8996\u89d2\u8d85\u904e 180 \u5ea6\u7684\u6642\u5019\u4ee5\u53e6\u4e00\u7a2e\u89e3\u6cd5, \u524d\u534a\u7531\u65bc\u5713\u578b\u534a\u5f91\u95dc\u4fc2\u5df2\u7d93\u5f97\u77e5\u6700\u9577\u5bec\u5ea6\u662f Radius * 2f<br>5)\u628a\u8d85\u904e\u7684\u89d2\u5ea6\u6e1b\u4e00\u500b 90 \u5ea6\u53c8\u53ef\u4ee5\u518d\u4f7f\u7528\u540c\u6a23\u624b\u6cd5\u67e5\u627e\u9918\u4e0b\u4e00\u534a\u7684\u6700\u77ed\u9577\u5ea6.<br>6)\u7136\u5f8c\u8207\u524d\u9762\u7684\u534a\u5f91\u76f8\u52a0\u5373\u53ef\u53d6\u5f97\u9019\u500b Overlap Box \u7684\u9577\u5bec<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u6210\u54c1 &#8211; \u58d3\u529b\u6e2c\u8a66<\/h3>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1920\" height=\"1121\" src=\"https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/ViewAngleStressTest50_01.png\" alt=\"\" class=\"wp-image-2484\" srcset=\"https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/ViewAngleStressTest50_01.png 1920w, https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/ViewAngleStressTest50_01-300x175.png 300w, https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/ViewAngleStressTest50_01-1024x598.png 1024w, https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/ViewAngleStressTest50_01-768x448.png 768w, https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/ViewAngleStressTest50_01-1536x897.png 1536w, https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/ViewAngleStressTest50_01-359x210.png 359w, https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2021\/05\/ViewAngleStressTest50_01-512x300.png 512w\" sizes=\"auto, (max-width: 1920px) 100vw, 1920px\" \/><figcaption>\u6210\u54c1\u7684\u58d3\u529b\u6e2c\u8a66.<\/figcaption><\/figure>\n\n\n\n<p>\u7c97\u7565\u7684\u5275\u5efa 50 \u500b\u9019\u7a2e\u5f62\u7684 Agent, \u5728 Deep Profile \u7684\u6e2c\u8a66\u4e0b.<br>\u7e3d\u5408\u7684 AvatarEye.Peep() \u5373\u672c\u7a0b\u5e8f 50 \u6b21\u7528\u4e86 13.07ms, 0gc<br>\u5176\u4e2d<br>Raycast call 359 \u6b21,\u7528\u4e86 1.29ms, 0gc<br>OverlapBox call 50 \u6b21,\u7528\u4e86 0.65ms, 0gc<br>\u5373\u662f Raycast \u7684\u6210\u672c\u53ea\u4f54\u6574\u9ad4\u7684 20% \u5176\u9918\u5c31\u662f\u904b\u7b97 vector \u53ca hashset \u7684\u5206\u4f48.<br>\u500b\u4eba\u89ba\u5f97\u6210\u7e3e\u9084\u662f\u4e0d\u932f\u7684\u7562\u7adf\u9084\u662f\u5728 main thread \u4e0a\u904b\u4f5c.<br>\u53ef\u4ee5\u518d\u9032\u4e00\u6b65\u7684\u58d3\u7e2e\u5c31\u662f\u7565\u53bb\u90a3\u5806\u8a08\u7b97 OverlapBox size \u7684 vector projection \u61c9\u8a72\u53ef\u4ee5\u66f4\u9032\u4e00\u6b65\u58d3\u7e2e\u6642\u9593,<br>\u4f46\u8003\u616e\u5230\u904e\u65bc\u7e41\u8907\u7684\u8a2d\u5b9a, \u4ee5\u53ca\u4f7f\u7528\u8005\u8a2d\u5b9a\u51fa\u932f\u6240\u884d\u751f\u7684\u554f\u984c&#8230; \u55ef, \u80fd\u4ee4\u6211\u61f6\u60f0\u7684\u9078\u64c7\u5c31\u662f\u597d\u9078\u64c7.<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-4-3 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"Vlog : Triangular field of view by BoxOverlap + Raycast\" width=\"1260\" height=\"945\" src=\"https:\/\/www.youtube.com\/embed\/0_BXQomebr0?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<p>\u4ee5\u4e0b\u5c31\u662f\u8996\u983b\u7248\u7684\u6e2c\u8a66\u4ee3\u78bc.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">using System.Collections;\nusing System.Collections.Generic;\nusing UnityEngine;\n\nnamespace Kit.Avatar\n{\n    public class AvatarEye : SensorBase, ICanSee\n    {\n        #region Debug\n        [System.Flags]\n        public enum eDebugDraw\n        {\n            Label = 1 &lt;&lt; 0,\n            VisiableArea = 1 &lt;&lt; 1,\n            InSightTarget = 1 &lt;&lt; 2,\n            OverlapSizeDebug = 1 &lt;&lt; 3,\n            OverlapBox = 1 &lt;&lt; 4,\n        }\n        [System.Serializable]\n        public class DebugInfo\n        {\n            [MaskField(typeof(eDebugDraw))] public eDebugDraw draw = (eDebugDraw)0;\n            public Vector2 labelOffset = Vector2.zero;\n            public Color areaColor = Color.yellow.CloneAlpha(.1f);\n            public Color targetColor = Color.red.CloneAlpha(.4f);\n            public Color halfExtendsColor = Color.cyan.CloneAlpha(.5f);\n            public Color overlapColor = Color.green.CloneAlpha(.5f);\n        }\n        [SerializeField] DebugInfo m_Debug = new DebugInfo();\n        private void DebugGizmos()\n        {\n            if ((int)m_Debug.draw == 0)\n                return;\n            if (!Application.isPlaying)\n                Peep();\n\n            Vector3 eyePos, boxCenter, halfExtends;\n            Quaternion orientation;\n\n            GetEyePivot(out Transform bone, out eyePos);\n            CalculateOverlapBox(eyePos, out boxCenter, out halfExtends, out orientation);\n\n            if (m_Debug.draw.HasFlag(eDebugDraw.VisiableArea))\n            {\n                Vector3 forward = orientation * Vector3.forward;\n                Vector3 up = orientation * Vector3.up;\n                GizmosExtend.DrawArc(eyePos, up, forward, m_ViewAngle * 0.5f, m_MaxDistance, m_Debug.areaColor);\n                GizmosExtend.DrawArc(eyePos, up, forward, m_ViewAngle * -0.5f, m_MaxDistance, m_Debug.areaColor);\n            }\n\n            if (m_Debug.draw.HasFlag(eDebugDraw.InSightTarget))\n            {\n                foreach (var t in m_TargetsInSight)\n                {\n                    GizmosExtend.DrawLine(eyePos, t.point, m_Debug.targetColor);\n                    GizmosExtend.DrawSphere(t.point, 0.05f, m_Debug.targetColor);\n                    if (m_Debug.draw.HasFlag(eDebugDraw.Label))\n                    {\n                        GizmosExtend.DrawLabel(t.point, t.ToString(), m_Debug.labelOffset);\n                    }\n                }\n            }\n\n            if (m_Debug.draw.HasFlag(eDebugDraw.OverlapBox))\n            {\n                GizmosExtend.DrawBox(boxCenter, halfExtends, orientation, m_Debug.overlapColor);\n            }\n        }\n        #endregion Debug\n\n        #region Setting\n        [Header(\"Eye Sight\")]\n        [SerializeField] private bool m_CanSee = false;\n\n        [SerializeField] private bool m_UseHumanoidBone = true;\n        [SerializeField] private HumanBodyBones m_HumanBodyBones = HumanBodyBones.Head;\n        [SerializeField] private Vector3 m_EyeOffset = Vector3.zero;\n        [SerializeField] private Vector3 m_BodyRotationOffset = Vector3.zero;\n        [SerializeField] private float m_MaxDistance = 1f;\n        [SerializeField, Range(0f, 360f)] float m_ViewAngle = 30f;\n\n        [Header(\"Optimization\")]\n        [SerializeField, Range(0.0001f, 0.1f)] private float m_Deviation = 0.05f;\n        [Help(\"Memory alloc for BoxOverlap detection.\", eMessageType.Info)]\n        [SerializeField] private int m_MemorySize = 5;\n        private Collider[] m_OverlapColliders = { };\n        private RaycastHit m_DirectHitTest;\n        [Tooltip(\"Based on FixedUpdate.\")]\n        [SerializeField] private float m_Interval = 0.02f;\n\n        [Header(\"Physics\")]\n        [Help(\"The layers of attackable target.\\nE.g. Player, Enemies...etc\")]\n        [SerializeField] private LayerMask m_TargetMask = Physics.DefaultRaycastLayers;\n        [Help(\"The layers of obstacle, anything that CAN NOT see through\\nE.g. Wall, Door\", eMessageType.Warning)]\n        [SerializeField] private LayerMask m_BlockableMask = Physics.IgnoreRaycastLayer;\n        [SerializeField] private QueryTriggerInteraction m_QueryTriggerInteraction = QueryTriggerInteraction.Collide;\n        #endregion Setting\n\n        #region System\n        protected override void OnEnable()\n        {\n            base.OnEnable();\n            \/\/ little hack to distribute see time\n            m_LastPeepTime = Time.timeSinceLevelLoad + Random.value;\n        }\n        private void OnDrawGizmos()\n        {\n            DebugGizmos();\n        }\n\n        private void FixedUpdate()\n        {\n            if (CanSee() &amp;&amp;\n                m_FrameCount != Time.frameCount &amp;&amp;\n                Time.timeSinceLevelLoad - m_LastPeepTime >= m_Interval)\n            {\n                m_LastPeepTime = Time.timeSinceLevelLoad;\n                m_FrameCount = Time.frameCount;\n                Peep();\n            }\n        }\n        #endregion System\n\n        #region Core\n        private HashSet&lt;TargetInSight> m_TargetsInSight = new HashSet&lt;TargetInSight>(new TargetInSightComparer());\n        \n        public ICollection&lt;TargetInSight> GetTargetsInSight()\n        {\n            return m_TargetsInSight;\n        }\n        private float m_LastPeepTime = 0f;\n        private int m_FrameCount = 0;\n\n        public bool CanSee()\n        {\n            return m_CanSee;\n        }\n\n        private void Peep()\n        {\n            \/\/ Allocate memory\n            if (m_OverlapColliders.Length != m_MemorySize)\n                m_OverlapColliders = new Collider[m_MemorySize];\n            m_TargetsInSight.Clear(); \/\/ clean previous result.\n\n            \/\/ Calculate area\n            GetEyePivot(out Transform bone, out Vector3 eyePos);\n            CalculateOverlapBox(eyePos, out Vector3 boxCenter, out Vector3 halfExtends, out Quaternion orientation);\n\n            \/\/ AABB test\n            int hitCount = Physics.OverlapBoxNonAlloc(boxCenter, halfExtends, m_OverlapColliders, orientation, m_TargetMask, m_QueryTriggerInteraction);\n            for (int i = 0; i &lt; hitCount &amp;&amp; i &lt; m_OverlapColliders.Length; i++)\n            {\n                if (m_OverlapColliders[i].transform.IsChildOf(avatar.transform))\n                    continue; \/\/ ignore common case to saw owner.\n                CheckIfColliderInSight(eyePos, orientation, orientation * Vector3.forward, m_OverlapColliders[i]);\n            }\n        }\n        private void CheckIfColliderInSight(Vector3 eyePos, Quaternion orientation, Vector3 facing, Collider collider)\n        {\n            Vector3 targetClosestPoint = collider.ClosestPoint(eyePos);\n            Vector3 sightVectorProjected = Vector3.ProjectOnPlane(targetClosestPoint - eyePos, orientation * Vector3.up);\n            float forwardAngle = Vector3.Angle(facing, sightVectorProjected);\n            if (forwardAngle &lt;= m_ViewAngle * 0.5f)\n            {\n                \/\/ Within view angle, check obstacle blocked or not.\n                int mix = m_BlockableMask.value | m_TargetMask.value;\n                if (Physics.Raycast(eyePos, sightVectorProjected, out m_DirectHitTest, m_MaxDistance, mix, m_QueryTriggerInteraction))\n                {\n                    \/\/ we should either hit obstacle layer, or target itself.\n                    if (m_TargetMask.Contain(m_DirectHitTest.transform.gameObject) || \/\/ is target\n                        m_DirectHitTest.transform.IsChildOf(collider.transform)) \/\/ is part of target\n                    {\n                        m_TargetsInSight.Add((TargetInSight)m_DirectHitTest); \/\/ Target in sight.\n                    } \/\/ else, it's blocked by obstacle.\n                }\n            }\/\/ else, common case, out of range angle \/ distance\n        }\n        private void GetEyePivot(out Transform bone, out Vector3 pos)\n        {\n            bone = transform;\n            if (m_UseHumanoidBone)\n            {\n                var tmp = avatar.avatarAnimator.GetBoneTransform(m_HumanBodyBones);\n                if (tmp) \/\/ in case fail to locate bone transform\n                    bone = tmp;\n            }\n\n            pos = bone.position;\n            if (m_EyeOffset != Vector3.zero)\n                pos = bone.TransformPoint(m_EyeOffset);\n        }\n        private void CalculateOverlapBox(Vector3 eyePos, out Vector3 boxCenter, out Vector3 halfExtends, out Quaternion orientation)\n        {\n            orientation = avatar.body.transform.rotation;\n            if (m_BodyRotationOffset != Vector3.zero)\n                orientation = avatar.body.transform.rotation * Quaternion.Euler(m_BodyRotationOffset);\n\n            float angle = m_ViewAngle * 0.5f;\n            Vector3 farPlanePivot = eyePos + orientation * Vector3.forward * m_MaxDistance;\n            if (angle &lt; 90f)\n            {\n#if TAN\n                \/\/ Since tan version require loopup table, it require CPU cost\n                float half = Mathf.Tan(Mathf.Deg2Rad * angle) * m_MaxDistance;\n                \/\/DebugExtend.DrawRay(farPlanePivot, orientation * Vector3.right * half, Color.cyan);\n                \/\/DebugExtend.DrawRay(farPlanePivot, orientation * Vector3.right * -half, Color.cyan);\n#else\n                Vector3 angleVector = orientation * Quaternion.Euler(0f, angle, 0f) * new Vector3(0f, 0f, m_MaxDistance);\n                Vector3 angleVectorEndPoint = eyePos + angleVector;\n                Vector3 projectToRight = Vector3.Project(angleVector, orientation * Vector3.right);\n                if (m_Debug.draw.HasFlag(eDebugDraw.OverlapSizeDebug))\n                {\n                    DebugExtend.DrawPoint(angleVectorEndPoint, m_Debug.halfExtendsColor, 0.5f);\n                    DebugExtend.DrawRay(angleVectorEndPoint, -projectToRight, m_Debug.halfExtendsColor);\n                }\n                float half = projectToRight.magnitude;\n#endif           \n                half = Mathf.Clamp(half, 0f, m_MaxDistance);\n                boxCenter = Vector3.Lerp(farPlanePivot, eyePos, 0.5f);\n                halfExtends = new Vector3(half, m_Deviation, m_MaxDistance * 0.5f);\n            }\n            else\n            {\n#if TAN\n                float angleBehind = angle - 90f;\n                float behind = Mathf.Atan(Mathf.Deg2Rad * angleBehind) * m_MaxDistance;\n#else\n                Vector3 angleVector = orientation * Quaternion.Euler(0f, angle, 0f) * new Vector3(0f, 0f, m_MaxDistance);\n                Vector3 angleVectorEndPoint = eyePos + angleVector;\n                Vector3 projectToBack = Vector3.Project(angleVector, orientation * Vector3.back);\n                if (m_Debug.draw.HasFlag(eDebugDraw.OverlapSizeDebug))\n                {\n                    DebugExtend.DrawPoint(angleVectorEndPoint, m_Debug.halfExtendsColor, 0.5f);\n                    DebugExtend.DrawRay(angleVectorEndPoint, -projectToBack, m_Debug.halfExtendsColor);\n                }\n                float behind = projectToBack.magnitude;\n#endif\n                behind = Mathf.Clamp(behind, 0f, m_MaxDistance);\n                Vector3 behindPlanePivot = eyePos + orientation * Vector3.back * behind;\n                boxCenter = Vector3.Lerp(farPlanePivot, behindPlanePivot, 0.5f);\n                halfExtends = new Vector3(m_MaxDistance, m_Deviation, (m_MaxDistance + behind) * 0.5f);\n            }\n        }\n        #endregion Core\n    }\n}<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">using System.Collections;\nusing System.Collections.Generic;\nusing UnityEngine;\n\nnamespace Kit.Avatar\n{\n    public class SensorBase : AvatarSubset { }\n\n    public struct TargetInSight :\n        IEqualityComparer&lt;TargetInSight>,\n        IEqualityComparer&lt;RaycastHit>\n    {\n        public Transform transform;\n        public Rigidbody rigidbody;\n        public ArticulationBody articulationBody;\n        public Collider collider;\n\n        public Vector3 point;\n        public Vector3 normal;\n        public float distance;\n\n        private bool m_GeneratedHashCode;\n        private int hashCode;\n\n        public bool Equals(TargetInSight x, TargetInSight y)\n        {\n            return x.collider.Equals(y.collider);\n        }\n        public int GetHashCode(TargetInSight obj)\n        {\n            if (!m_GeneratedHashCode)\n                hashCode = obj.collider.GetHashCode();\/\/Extensions.GenerateHashCode(obj.collider,obj.distance,obj.point);\n            return hashCode;\n        }\n\n        public bool Equals(RaycastHit x, RaycastHit y)\n        {\n            return x.collider.Equals(y.collider);\n        }\n        public int GetHashCode(RaycastHit obj)\n        {\n            if (!m_GeneratedHashCode)\n                hashCode = obj.collider.GetHashCode();\n            return hashCode;\n        }\n\n        public override int GetHashCode()\n        {\n            if (!m_GeneratedHashCode)\n                hashCode = collider.GetHashCode();\n            return hashCode;\n        }\n\n        public override string ToString()\n        {\n            return $\"[TargetInSign:{transform}\\nPoint:{point}\\nDistance:{distance:F2}]\";\n        }\n        public static implicit operator TargetInSight (RaycastHit raycastHit)\n        {\n            return new TargetInSight\n            {\n                transform = raycastHit.transform,\n                rigidbody = raycastHit.rigidbody,\n                articulationBody = raycastHit.articulationBody,\n                collider = raycastHit.collider,\n                point = raycastHit.point,\n                normal = raycastHit.normal,\n                distance = raycastHit.distance,\n            };\n        }\n        public static explicit operator Transform(TargetInSight targetInSign)\n        {\n            return targetInSign.transform;\n        }\n    }\n    public class TargetInSightComparer : IEqualityComparer&lt;TargetInSight>\n    {\n        public bool Equals(TargetInSight x, TargetInSight y)\n        {\n            return x.collider.Equals(y.collider);\n        }\n\n        public int GetHashCode(TargetInSight obj)\n        {\n            return obj.collider.GetHashCode();\n        }\n    }\n}<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>\u5f88\u4e45\u4ee5\u524d\u5beb\u904e\u4e00\u7bc7 \u4e8c\u5206\u6cd5, \u969c\u7919\u7269\u908a\u6cbf\u67e5\u627e &amp; \u8cc7\u6e90\u7ba1\u7406 \u7d50\u679c\u597d\u50cf\u4e0d\u5c0f\u5fc3\u8aa4\u5c0e\u4e86\u4e00\u4e9b\u7db2\u53cb\u4ee5\u70ba\u9019 &hellip;<\/p>\n","protected":false},"author":1,"featured_media":2491,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[78,109,3],"tags":[],"class_list":["post-2481","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-math","category-vector","category-public"],"_links":{"self":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts\/2481","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/comments?post=2481"}],"version-history":[{"count":18,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts\/2481\/revisions"}],"predecessor-version":[{"id":2517,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts\/2481\/revisions\/2517"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/media\/2491"}],"wp:attachment":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/media?parent=2481"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/categories?post=2481"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/tags?post=2481"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}