尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Unity 3D中JavaScript驱动自定义机器人:架构设计与实战

Unity 3D中JavaScript驱动自定义机器人:架构设计与实战 1. 项目概述与核心思路最近在社区里看到不少朋友对Unity 3D中结合JavaScript特别是通过Unity的JavaScript/UnityScript支持或与WebGL交互来实现自定义机器人逻辑的项目很感兴趣尤其是“CR-Clone”这类听起来就很有挑战性的项目。作为一个在Unity和前端技术栈里摸爬滚打了多年的开发者我深知这里面既有Unity强大的3D表现力带来的兴奋也有脚本语言选择、跨语言交互带来的“坑”。今天我就结合“CR-Clone”这个自定义机器人克隆项目的实战经验来和大家深入聊聊如何在Unity 3D环境下用JavaScript或相关技术驱动一个可自定义行为的机器人从环境搭建、核心架构到具体实现和避坑指南手把手带你走一遍。简单来说这个项目的核心目标是在Unity 3D引擎中创建一个机器人实体其核心行为逻辑不是用传统的C#编写而是通过JavaScript来定义和控制从而实现高度的灵活性和动态可配置性也就是所谓的“克隆”一个可编程的机器人行为模型。这听起来可能有点“跨界”但在教育、快速原型验证、或者需要与Web前端深度集成的项目比如在WebGL构建中动态加载行为脚本中这种架构有其独特的价值。它适合那些已经熟悉JavaScript/TypeScript并希望将其逻辑能力带入3D交互场景的开发者也适合想探索Unity更多可能性的技术爱好者。2. 技术选型与架构设计解析2.1 为什么选择JavaScript而非C#在Unity的主流生态里C#无疑是亲儿子性能好、工具链完善。那么为什么还要考虑用JavaScript呢这背后有几个关键考量点也是这个项目的出发点。首先是团队技能栈的复用。如果你的团队核心是Web前端开发者对JavaScript/TypeScript了如指掌但对C#和Unity的特定API感到陌生。那么让机器人行为逻辑这层“业务代码”用JavaScript来写可以极大降低学习成本和开发门槛。他们可以专注于机器人AI、状态机、行为树等逻辑设计而不必深入钻研Unity的物理系统或渲染管线细节。其次是动态性与热更新的需求。C#脚本在Unity中编译成DLL虽然性能卓越但修改后需要重新编译和重启/重载场景。而JavaScript尤其是在WebGL环境下或通过特定解释器可以实现真正的运行时动态加载和解析。想象一下你的机器人战斗游戏上线后发现某个Boss的AI行为有缺陷你完全可以通过服务器下发一段新的JavaScript代码片段客户端加载后立即生效无需用户更新整个游戏包。这对于需要频繁调整逻辑的在线服务或教育模拟软件来说是巨大的优势。最后是与Web生态的深度融合。如果你的项目最终目标是发布为WebGL或者需要与网页端的控制面板、数据分析工具进行紧密交互那么使用JavaScript作为逻辑层可以无缝对接。你可以轻松地将网页中的配置参数、用户输入事件直接映射到3D机器人的行为上反之亦然将机器人的状态数据实时推送回网页进行可视化展示。当然这里的“JavaScript”需要明确一下。Unity历史上曾支持过名为“UnityScript”的类JavaScript语言但官方早已停止支持并推荐转向C#。因此在现代Unity项目中我们通常不是直接使用UnityScript而是通过以下几种方式实现“JavaScript逻辑控制”WebGL JavaScript Interop将项目发布为WebGL然后通过Unity的SendMessage或更现代的jslib插件与页面中的JavaScript代码通信让JavaScript驱动Unity内部对象。第三方JavaScript引擎集成在Unity中集成如ClearScriptV8引擎封装、Jint.NET的JavaScript解释器等库在运行时解析和执行JavaScript代码并使其能够调用Unity的C# API。TypeScript编译为C#/Wasm使用像UnityTS这样的工具将TypeScript代码编译成C#或WebAssembly在保持TypeScript开发体验的同时获得接近原生C#的性能。对于“CR-Clone”这类强调自定义和克隆的项目我倾向于第二种或结合第一种方案因为它提供了最大的灵活性和运行时动态能力。接下来我们就基于集成Jint引擎的方案来拆解整个架构。2.2 核心架构设计分层与通信一个健壮的、基于JavaScript的自定义机器人系统不能是铁板一块。我们需要清晰的分层确保职责分离便于维护和扩展。我设计的核心架构分为四层1. Unity表现层 (Unity Presentation Layer)职责负责机器人的3D模型渲染、动画播放、物理碰撞、音效播放等所有与Unity引擎直接交互的部分。实现由标准的C#MonoBehaviour脚本构成。例如RobotView.cs控制模型和动画RobotPhysics.cs处理刚体和碰撞。2. 行为接口层 (Behavior Interface Layer)职责定义机器人所有可被外部控制的行为和可查询状态的抽象接口。这是C#与JavaScript通信的“契约”或“桥梁”。实现一组纯C#接口或抽象类。例如定义一个IRobotController接口包含Move(Vector3 direction),Rotate(float angle),GetSensorData(string sensorName)等方法。这层不包含具体实现只声明功能。3. JavaScript引擎适配层 (JavaScript Engine Adapter Layer)职责集成JavaScript引擎如Jint并负责将行为接口层暴露给JavaScript上下文。同时它也负责执行JavaScript代码、处理异常、管理脚本生命周期。实现一个核心的C#类例如JsRobotBridge.cs。它内部实例化Jint引擎将C#对象实现了上述接口的实例注入到引擎中让JavaScript代码可以像调用普通JS函数一样调用机器人的移动、转向等方法。4. 自定义脚本层 (Custom Script Layer)职责由用户或开发者编写的纯JavaScript代码定义了机器人的具体行为逻辑。例如巡逻逻辑、追击逻辑、避障算法等。实现普通的.js或.txt文件作为TextAsset资源被Unity加载然后传递给适配层执行。这个架构的关键在于表现层和脚本层完全解耦。你可以更换机器人的3D模型而不影响AI逻辑也可以重写JavaScript脚本彻底改变机器人行为而无需改动Unity的C#核心代码。通信流程是JavaScript脚本通过引擎适配层调用接口层的方法接口层的方法再驱动表现层的具体实现从而让机器人在3D世界中动起来。注意这里有一个重要的设计决策是否允许JavaScript直接访问Unity的API如GameObject,Transform我强烈建议不要这样做。这会将JavaScript脚本与Unity引擎深度耦合失去跨平台和可替换性并带来严重的安全风险恶意脚本可能破坏场景。应该通过我们定义的接口层来提供安全的、受控的访问。3. 环境搭建与核心工具链配置3.1 Unity项目初始化与Jint引擎集成首先创建一个新的Unity项目建议使用2021 LTS或更新版本以获得更好的稳定性和包管理支持。然后我们需要集成Jint引擎。通过Unity Package Manager (UPM) 安装Jint这是最推荐的方式。打开Window - Package Manager点击左上角的“”号选择“Add package from git URL...”。Jint的Git仓库地址通常是https://github.com/sebastienros/jint.git。你也可以先通过NuGet获取Jint的DLL然后放入项目的Plugins文件夹但UPM方式依赖管理更清晰。验证安装安装完成后你可以尝试在任意C#脚本中写入using Jint;如果没有报错说明集成成功。创建核心桥接类在项目中创建Scripts/Runtime/JsRobotBridge.cs。这个类将是整个系统的中枢。using UnityEngine; using Jint; using System; using System.IO; public class JsRobotBridge : MonoBehaviour { private Engine _jsEngine; private IRobotController _robotController; // 注入的机器人控制器 void Awake() { // 初始化Jint引擎 _jsEngine new Engine(cfg { cfg.AllowClr(); // 允许访问CLR.NET类型需谨慎 // 可以在这里限制访问的命名空间增强安全性 }); // 获取关联的机器人控制器可以通过GetComponent或其他依赖注入方式 _robotController GetComponentIRobotController(); if (_robotController null) { Debug.LogError(JsRobotBridge requires an IRobotController component.); return; } // 将机器人控制器实例注入JS环境命名为“robot” _jsEngine.SetValue(robot, _robotController); // 注入一些常用的工具函数或常量例如Vector3的简化创建方法 _jsEngine.SetValue(vec3, new Funcfloat, float, float, Vector3((x, y, z) new Vector3(x, y, z))); _jsEngine.SetValue(debugLog, new Actionobject(Debug.Log)); } // 加载并执行一段JavaScript代码 public void ExecuteScript(string scriptContent) { if (_jsEngine null) return; try { _jsEngine.Execute(scriptContent); } catch (Exception e) { Debug.LogError($JavaScript执行错误: {e.Message}); } } // 从TextAsset加载并执行 public void ExecuteScript(TextAsset scriptAsset) { if (scriptAsset ! null) { ExecuteScript(scriptAsset.text); } } // 每帧更新可以在这里驱动JS中的“update”函数 void Update() { if (_jsEngine ! null) { try { // 调用JS中定义的update函数如果存在的话 _jsEngine.Invoke(update, Time.deltaTime); } catch {} } } }3.2 定义机器人行为接口IRobotController在Scripts/Runtime/Interfaces/目录下创建IRobotController.cs。这是契约的核心。using UnityEngine; public interface IRobotController { // 移动控制 void Move(Vector3 direction); void MoveTo(Vector3 worldPosition); void Stop(); // 旋转控制 void Rotate(float deltaAngleDegrees); // 绕Y轴旋转 void LookAt(Vector3 worldPosition); // 动作控制 void PerformAction(string actionName); // 传感器数据获取 float GetDistance(string sensorName); // 例如 “Front”, “Left” bool HasLineOfSightTo(Vector3 targetPosition); // 状态获取 Vector3 GetPosition(); float GetHealth(); void SetHealth(float value); // 事件供JS查询 bool IsEventTriggered(string eventName); void ClearEvent(string eventName); }然后你需要创建一个具体的C#类来实现这个接口例如StandardRobotController.cs。这个类会挂载在机器人GameObject上直接操作Transform、Rigidbody、触发动画等。3.3 配置VS Code作为JavaScript开发环境虽然Unity内置编辑器可以编辑文本但对于复杂的JavaScript逻辑一个强大的IDE是必不可少的。VS Code是绝佳选择。安装VS Code从官网下载安装。安装必备插件ESLint代码质量和风格检查。你需要一个.eslintrc.js配置文件来定义规则。JavaScript (ES6) code snippets提供代码片段加速开发。Code Runner可以快速运行选中的JS代码片段虽然不能直接调用Unity API但可用于测试纯算法逻辑。Unity Tools虽然不是必须但可以提供一些Unity相关的代码片段。配置工作区在你的Unity项目根目录下创建一个Scripts/JavaScript/文件夹来存放所有自定义的机器人行为脚本。用VS Code打开这个文件夹作为工作区。初始化Node.js项目可选但推荐在JavaScript/目录下运行npm init -y然后安装types/unity如果有社区维护的类型定义包或创建自己的d.ts声明文件为你的IRobotController接口提供TypeScript类型提示这样在写JS时能有智能补全。例如创建一个unity.d.ts文件声明declare const robot: IRobotController;。4. 核心功能模块实现详解4.1 机器人基础运动控制的JavaScript实现现在让我们编写第一个机器人行为脚本一个让机器人随机巡逻的脚本。在Scripts/JavaScript/下创建PatrolBehavior.js并将其导入Unity作为TextAsset。// PatrolBehavior.js // 随机巡逻行为 // 状态变量 var patrolPoints []; var currentPatrolIndex 0; var reachThreshold 0.5; // 到达点的距离阈值 var isMoving false; // 初始化函数会在脚本加载后由C#桥接器调用 function initialize() { debugLog(巡逻行为脚本初始化); // 假设我们通过robot对象设置几个巡逻点这里硬编码实际可以从C#注入 patrolPoints [ vec3(0, 0, 0), vec3(5, 0, 0), vec3(5, 0, 5), vec3(0, 0, 5) ]; currentPatrolIndex 0; isMoving false; debugLog(巡逻点设置完毕共 patrolPoints.length 个点); } // 每帧更新的逻辑 function update(deltaTime) { if (!isMoving patrolPoints.length 0) { // 开始向下一个点移动 var targetPos patrolPoints[currentPatrolIndex]; robot.MoveTo(targetPos); isMoving true; debugLog(开始向巡逻点 currentPatrolIndex 移动: targetPos); } if (isMoving) { var currentPos robot.GetPosition(); var targetPos patrolPoints[currentPatrolIndex]; var distance Math.sqrt( Math.pow(targetPos.x - currentPos.x, 2) Math.pow(targetPos.z - currentPos.z, 2) // 忽略Y轴高度差 ); if (distance reachThreshold) { // 到达当前巡逻点 debugLog(到达巡逻点 currentPatrolIndex); robot.Stop(); isMoving false; // 切换到下一个点循环 currentPatrolIndex (currentPatrolIndex 1) % patrolPoints.length; // 可以在这里添加一个等待时间 // setTimeout(function(){ isMoving false; }, 2000); // Jint中setTimeout可能不可用需用C#侧计时器模拟 } } } // 供外部调用的函数例如被攻击时中断巡逻 function onInterrupted() { debugLog(巡逻被中断); robot.Stop(); isMoving false; } // 在C#中调用此函数来启动脚本 initialize();在Unity中你需要将这段代码复制到一个TextAsset文件中或者直接创建一个.txt文件将内容粘贴进去然后拖入Resources文件夹或任意可被Resources.Load加载的路径。然后修改JsRobotBridge.cs在Start()方法中加载并执行它。public TextAsset patrolScriptAsset; // 在Inspector中拖入PatrolBehavior.js对应的TextAsset void Start() { // ... Awake中初始化引擎 ... if (patrolScriptAsset ! null) { ExecuteScript(patrolScriptAsset); } }关键点解析robot对象是在C#桥接器中注入的全局变量对应实现了IRobotController的C#对象。vec3和debugLog也是C#注入的辅助函数简化了JS中的操作。update函数会在JsRobotBridge的Update()中被每帧调用并传入Time.deltaTime。注意在Jint中原生的setTimeout可能无法直接使用因为缺乏浏览器环境的事件循环。如果需要延时一种常见的做法是在C#桥接器中实现一个简单的计时器系统并向JS环境暴露setTimeout和clearTimeout的模拟函数。4.2 传感器数据集成与决策逻辑一个智能机器人需要感知环境。我们在IRobotController中定义了GetDistance等方法。现在在C#的StandardRobotController中实现它们。例如通过Raycast射线检测来模拟距离传感器。// StandardRobotController.cs 部分代码 public class StandardRobotController : MonoBehaviour, IRobotController { public Transform frontSensorOrigin; public float sensorMaxRange 10f; public LayerMask obstacleLayer; public float GetDistance(string sensorName) { Transform sensorTransform null; switch(sensorName.ToLower()) { case front: sensorTransform frontSensorOrigin; break; // ... 其他传感器 default: return sensorMaxRange; // 默认返回最大值 } if (sensorTransform null) return sensorMaxRange; RaycastHit hit; if (Physics.Raycast(sensorTransform.position, sensorTransform.forward, out hit, sensorMaxRange, obstacleLayer)) { return hit.distance; } return sensorMaxRange; } // ... 实现其他接口方法 }然后在JavaScript中我们就可以基于传感器数据做出更复杂的决策。创建一个AdvancedPatrolBehavior.js实现带避障的巡逻。// AdvancedPatrolBehavior.js // 带避障的智能巡逻 var state PATROLLING; // 状态机: PATROLLING, AVOIDING, ROTATING var avoidDirection 0; // -1: 左转, 1: 右转 var rotateTimer 0; function update(deltaTime) { switch(state) { case PATROLLING: // 基本的巡逻逻辑... var frontDist robot.GetDistance(front); var leftDist robot.GetDistance(left); var rightDist robot.GetDistance(right); if (frontDist 2.0) { // 前方有障碍进入避障状态 debugLog(检测到障碍距离: frontDist); robot.Stop(); // 简单策略哪边空间大往哪边转 avoidDirection (leftDist rightDist) ? -1 : 1; state ROTATING; rotateTimer 0.5; // 旋转0.5秒 debugLog(开始转向避障方向: (avoidDirection-1?左:右)); } break; case ROTATING: rotateTimer - deltaTime; robot.Rotate(90 * avoidDirection * deltaTime); // 每秒旋转90度 if (rotateTimer 0) { state PATROLLING; debugLog(转向完成恢复巡逻); } break; case AVOIDING: // 可以更复杂的沿墙走逻辑 break; } }4.3 行为克隆与动态脚本加载机制“克隆”项目的精髓在于行为的可复制和动态替换。我们可以设计一个RobotBehaviorManager来管理多个行为脚本。// RobotBehaviorManager.cs public class RobotBehaviorManager : MonoBehaviour { public JsRobotBridge jsBridge; public ListTextAsset behaviorScripts; // 预加载的行为脚本库 private Dictionarystring, TextAsset _behaviorLibrary new Dictionarystring, TextAsset(); private string _currentBehaviorId; void Start() { // 初始化行为库 foreach(var script in behaviorScripts) { if(script ! null) { _behaviorLibrary[script.name] script; // 以文件名作为ID } } // 加载默认行为 LoadBehavior(PatrolBehavior); } // 动态加载并执行一个行为脚本 public void LoadBehavior(string behaviorId) { if (_behaviorLibrary.TryGetValue(behaviorId, out TextAsset script)) { jsBridge.ExecuteScript(script); _currentBehaviorId behaviorId; Debug.Log($已切换机器人行为至: {behaviorId}); } else { Debug.LogWarning($未找到行为脚本: {behaviorId}); } } // 从网络或本地文件动态加载新脚本实现热更新 public IEnumerator LoadBehaviorFromURL(string url) { using (UnityWebRequest webRequest UnityWebRequest.Get(url)) { yield return webRequest.SendWebRequest(); if (webRequest.result UnityWebRequest.Result.Success) { string jsCode webRequest.downloadHandler.text; // 可以在这里对jsCode进行安全校验 jsBridge.ExecuteScript(jsCode); Debug.Log(从网络动态加载行为脚本成功); } else { Debug.LogError($加载脚本失败: {webRequest.error}); } } } }这样在游戏运行时你可以通过调用LoadBehavior(“ChaseBehavior”)来立刻让机器人从巡逻切换到追击状态或者通过LoadBehaviorFromURL从服务器获取最新的AI脚本实现真正的“行为克隆”与动态部署。5. 调试、优化与常见问题排查5.1 JavaScript代码调试技巧在Unity中调试JavaScript代码不像C#那么直观但有以下几种有效方法Console.Log大法通过C#注入的debugLog函数对应Debug.Log将信息输出到Unity Console。这是最基本也是最常用的方法。在关键分支、变量变化处打印信息。利用Jint的异常信息Jint引擎会抛出包含行号和错误信息的.NET异常。确保在ExecuteScript方法中用try-catch包裹并将详细的错误信息如e.ToString()打印出来这能帮你快速定位语法错误或运行时错误。在VS Code中调试纯逻辑将你的机器人行为逻辑中与环境交互的部分如robot.MoveTo抽象或模拟掉然后在Node.js环境下运行和调试核心算法如状态机、路径计算。这能极大提高逻辑开发的效率。创建可视化调试工具在Unity编辑器中编写一个简单的调试UI可以实时显示JavaScript中重要变量的值、当前状态等。你甚至可以通过C#桥接器将JS中的全局变量暴露给这个UI。5.2 性能优化要点在Unity中运行JavaScript解释器是有开销的尤其是每帧都执行复杂脚本时。减少每帧的JS调用不要在JS的update函数里做大量计算或频繁调用C#接口。将一些计算转移到C#端或者降低JSupdate的调用频率例如每3帧执行一次。预编译与缓存Jint的Engine.Execute每次都会解析脚本。对于不会改变的脚本可以在初始化时执行一次将需要重复调用的函数如update的委托缓存起来后续直接调用委托避免重复解析。private Actionfloat _cachedUpdateAction; public void ExecuteAndCacheUpdate(string scriptContent) { _jsEngine.Execute(scriptContent); // 尝试获取JS中定义的update函数并转换为C#委托 if (_jsEngine.GetValue(“update”) ! null _jsEngine.GetValue(“update”).IsActionfloat()) { _cachedUpdateAction _jsEngine.GetValue(“update”).AsActionfloat(); } } void Update() { _cachedUpdateAction?.Invoke(Time.deltaTime); }限制脚本能力在创建Jint引擎时通过配置严格限制其可访问的CLR类型和命名空间。不要使用cfg.AllowClr()开放全部权限这既危险又可能引入不必要的性能开销和内存占用。只注入必需的API。注意内存泄漏JavaScript引擎中创建的对象特别是闭包引用的对象可能不会被.NET GC及时回收。对于长期运行的游戏如果频繁加载/卸载不同的行为脚本需要确保旧的引擎实例或脚本上下文被正确释放。5.3 常见问题与解决方案速查表问题现象可能原因解决方案Unity崩溃或无响应JavaScript代码陷入死循环或执行了耗时极长的同步操作。1. 在JS代码中加入简单的“心跳”检查或超时机制复杂。2. 更实际的方法是确保JS脚本的逻辑是分帧友好的避免while(true)这类结构。在C#桥接器端可以设置一个最大执行时间阈值超时则中断执行。robot对象为undefinedC#桥接器中未成功将控制器实例注入JS环境或注入的变量名不一致。1. 检查JsRobotBridge.Awake()中_robotController是否成功获取。2. 检查_jsEngine.SetValue(“robot”, …)这行代码是否执行。3. 在JS脚本开头用debugLog(typeof robot)打印验证。调用robot.MoveTo()后机器人不动C#端IRobotController.MoveTo的实现可能有bug或者JS传入的参数格式不对。1. 在C#的MoveTo方法中添加Debug.Log确认方法被调用且参数正确。2. 检查JS中传入的Vector3对象是否有效。确保vec3辅助函数工作正常。3. 检查机器人GameObject上的物理组件如Rigidbody、NavMeshAgent是否配置正确。JavaScript代码修改后不生效Unity可能没有重新编译包含JS代码的TextAsset文件或者脚本没有被重新加载。1. 确保JS文件是作为TextAsset导入的。修改后在Unity Project面板中选中该文件按CtrlR(或右键Reimport)强制重新导入。2. 如果是在运行时动态加载确保你的管理类如RobotBehaviorManager重新执行了ExecuteScript。发布到WebGL后JS交互失败WebGL环境与编辑器环境不同jslib插件或JavaScript与C#的互操作方式需要特殊处理。1. 如果使用本文的Jint方案请注意Jint可能不完全支持WebGL因为涉及即时编译。WebGL发布可能需要改用UnityWebRequest下载JS代码并通过eval在浏览器端执行再通过SendMessage与Unity内容通信。这是另一套架构需要提前规划。2. 彻底测试WebGL构建下的所有功能。内存占用持续增长JavaScript引擎中累积了未释放的对象或者C#端对JS函数/值的引用未被释放。1. 在切换行为脚本时考虑创建一个新的Engine实例替换旧的并确保旧实例被置为null。2. 避免在C#中长期持有对JS函数Delegate的强引用。使用弱引用或定期清理。6. 项目扩展与进阶思路实现基础版本后这个“CR-Clone”项目还有巨大的扩展空间可以朝着更专业、更强大的方向发展。1. 行为树与状态机的可视化编辑目前行为逻辑写在JS文件里。可以开发一个Unity编辑器扩展用节点图的方式可视化编辑行为树或状态机然后将其导出为JSON配置或直接编译成优化的JavaScript代码。这样即使是不懂编程的设计师也能参与机器人行为的制作。2. 集成机器学习库探索将TensorFlow.js或ONNX Runtime集成到Unity中。你的JavaScript行为脚本可以加载一个预训练的模型.pb或.onnx格式在update函数中获取传感器数据作为输入运行模型推理并将输出如移动指令传递给robot接口。这为实现基于学习的AI行为打开了大门。3. 多机器人协同与通信扩展IRobotController接口增加BroadcastMessage(string msg)和ReceiveMessages()等方法。在JavaScript中你可以让机器人之间通过发送和接收消息来协同工作例如实现编队移动、分工合作等复杂群体行为。4. 完整的沙盒与教学系统围绕这个框架构建一个完整的机器人编程沙盒。提供丰富的传感器模拟视觉、声音、触觉、多种环境道具、任务目标寻宝、竞速、战斗并配套详细的教程和关卡。这非常适合用于STEM教育或AI算法验证平台。踩坑心得这个项目最大的挑战不在于Unity或JavaScript本身而在于两者之间清晰、安全、高效的边界划分。初期我试图让JS拥有太多权力导致脚本难以管理和调试且安全隐患大。后来严格遵循“接口契约”模式C#只暴露必要的、安全的操作所有核心引擎功能都牢牢掌握在C#手中JS只做“决策”系统才变得稳定和可控。另外对于性能一定要有“解释器开销”的意识复杂计算能放C#就别放JSJS脚本的逻辑应尽量轻量、事件驱动。
返回列表