本篇内容主要讲解“用C#写的协程转换成JavaScript后无法正常工作怎么办”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“用C#写的协程转换成JavaScript后无法正常工作怎么办”吧!
先说结论吧:用C#写的协程转换成 JavaScript 后,无法正常工作,必须要手动修改一点点代码。
以下是 TestCoroutine.cs 代码:
1 [JsType(JsMode.Clr,"../../../StreamingAssets/JavaScript/SharpKitGenerated/JSBinding/Samples/Coroutine/TestCoroutine.javascript")] 2 public class TestCoroutine : MonoBehavIoUr { 3 4 // Use this for initialization 5 void Start () 6 { 7 StartCoroutine(Dotest()); 8 } 9 10 // Update is called once per frame 11 void Update () 12 { 13 14 } 15 void LateUpdate() 16 { 17 jsimp.Coroutine.UpdateMonoBehavIoUrCoroutine(this); 18 } 19 IEnumerator WaitForCangJingKong() 20 { 21 yield return new WaitForSeconds(2f); 22 } 23 IEnumerator Dotest() 24 { 25 // test null 26 Debug.Log(1); 27 yield return null; 28 29 // test WaitForSeconds 30 Debug.Log(2); 31 yield return new WaitForSeconds(1f); 32 33 // test WWW 34 WWW www = new WWW("file://" + Application.dataPath + "/JSBinding/Samples/Coroutine/CoroutineReadme.txt"); 35 yield return www; 36 Debug.Log("Text from WWW: " + www.text); 37 38 // test another coroutine 39 yield return StartCoroutine(WaitForCangJingKong()); 40 Debug.Log("Wait for CangJingKong finished!"); 41 } 42 }
这是 SharpKit 编译后的代码:
1 if (typeof(JsTypes) == "undefined") 2 var JsTypes = []; 3 var TestCoroutine = { 4 fullname: "TestCoroutine", 5 baseTypeName: "UnityEngine.MonoBehavIoUr", 6 assemblyName: "SharpKitProj", 7 Kind: "Class", 8 deFinition: { 9 ctor: function (){ 10 UnityEngine.MonoBehavIoUr.ctor.call(this); 11 }, 12 Start: function (){ 13 this.StartCoroutine$$IEnumerator(this.Dotest()); 14 }, 15 Update: function (){ 16 }, 17 LateUpdate: function (){ 18 jsimp.Coroutine.UpdateMonoBehavIoUrCoroutine(this); 19 }, 20 WaitForCangJingKong: function (){ 21 var $yield = []; 22 $yield.push(new UnityEngine.WaitForSeconds.ctor(2)); 23 return $yield; 24 }, 25 DoTest: function (){ 26 var $yield = []; 27 UnityEngine.Debug.Log$$Object(1); 28 $yield.push(null); 29 UnityEngine.Debug.Log$$Object(2); 30 $yield.push(new UnityEngine.WaitForSeconds.ctor(1)); 31 var www = new UnityEngine.WWW.ctor$$String("file://" + UnityEngine.Application.get_dataPath() + "/JSBinding/Samples/Coroutine/CoroutineReadme.txt"); 32 $yield.push(www); 33 UnityEngine.Debug.Log$$Object("Text from WWW: " + www.get_text()); 34 $yield.push(this.StartCoroutine$$IEnumerator(this.WaitForCangJingKong())); 35 UnityEngine.Debug.Log$$Object("Wait for CangJingKong finished!"); 36 return $yield; 37 } 38 } 39 }; 40 JsTypes.push(TestCoroutine);
注意看 DoTest 函数和 WaitForCangJingKong 函数,他们都是协程函数。SharpKit 对其中的 yield 代码翻译成一个 $yield 数组,每一个 yield 指令都加到 $yield 数组中。
这样使得我们无法与 JavaScript 的 yield 对接。这就是为什么协程编译成 JavaScript 代码后无法直接使用的原因。
目前,需要做点小修改就可以运行了,以下是修改过的 JavaScript 文件:
1 if (typeof(JsTypes) == "undefined") 2 var JsTypes = []; 3 var TestCoroutine = { 4 fullname: "TestCoroutine", 5 baseTypeName: "UnityEngine.MonoBehavIoUr", 6 assemblyName: "SharpKitProj", 7 Kind: "Class", 8 deFinition: { 9 ctor: function (){ 10 UnityEngine.MonoBehavIoUr.ctor.call(this); 11 }, 12 Start: function (){ 13 this.StartCoroutine$$IEnumerator(this.Dotest()); 14 }, 15 Update: function (){ 16 }, 17 LateUpdate: function (){ 18 jsimp.Coroutine.UpdateMonoBehavIoUrCoroutine(this); 19 }, 20 WaitForCangJingKong: function* (){ 21 22 yield (new UnityEngine.WaitForSeconds.ctor(2)); 23 24 }, 25 DoTest: function* (){ 26 27 UnityEngine.Debug.Log$$Object(1); 28 yield (null); 29 UnityEngine.Debug.Log$$Object(2); 30 yield (new UnityEngine.WaitForSeconds.ctor(1)); 31 var www = new UnityEngine.WWW.ctor$$String("file://" + UnityEngine.Application.get_dataPath() + "/JSBinding/Samples/Coroutine/CoroutineReadme.txt"); 32 yield (www); 33 UnityEngine.Debug.Log$$Object("Text from WWW: " + www.get_text()); 34 yield (this.StartCoroutine$$IEnumerator(this.WaitForCangJingKong())); 35 UnityEngine.Debug.Log$$Object("Wait for CangJingKong finished!"); 36 37 } 38 } 39 }; 40 JsTypes.push(TestCoroutine);
需要修改的有:
===================================================
2015/07/13 22:18 更新,目前已经把这个替换工作做到菜单了,菜单是 JSB | Correct JavaScript Yield code
这个菜单会尝试替换所有在 JSBindingSetting.jsDir 目录下的所有 JavaScript 文件。你只需要在编译 SharpKit 工程后运行一下这个菜单即可,如果有错误会给出提示,如果没错,代码应该可以正常使用了!
目前这个方案算是比较完美了。
下面讲一讲原理。
当我们在 C# 中使用 MonoBehavIoUr.StartCoroutine 函数时,传递给他代表协程函数的 IEnumerator(后面简称 IE)。之后是由 Unity 内部决定何时调用 IE.MoveNext()。而这部分的源代码我们是无法得到的。
在 JavaScript 端写了一个模拟 Unity 功能的协程管理器。文件是:
StreamingAssets/JavaScript/Manual/UnityEngine_MonoBehavIoUr.javascript
(后面简称 B)。
这里顺便提一下,当你导出 MonoBehavIoUr 类时,会产生
StreamingAssets/JavaScript/Generated/UnityEngine_MonoBehavIoUr.javascript
文件,简称A。B 和 A 的关系是,在includes.javascript 中,包含顺序是先 A 后 B,B重写了一些 A 的函数,并增加了一些内部函数。目前重写的函数只有 StartCoroutine$$IEnumerator 和 StartCoroutine$$String。增加的函数有 $UpdateallCoroutines,$updateCoroutine等等,这些就是协程管理器的内容。以下贴出代码(可能不是最新的):
1 _jstype = undefined; 2 for (var i = 0; i < JsTypes.length; i++) { 3 if (JsTypes[i].fullname == "UnityEngine.MonoBehavIoUr") { 4 _jstype = JsTypes[i]; 5 break; 6 } 7 } 8 9 if (_jstype) { 10 _jstype.deFinition.StartCoroutine$$String = function(a0/*String*/) { 11 if (this[a0]) 12 { 13 var fiber = this[a0].call(this); 14 return this.$AddCoroutine(fiber); 15 } 16 } 17 _jstype.deFinition.StartCoroutine$$IEnumerator = function(a0/*IEnumerator*/) { 18 return this.$AddCoroutine(a0); 19 } 20 21 // 22 // Coroutine Scheduler 23 // 24 // REFERENCE FROM 25 // 26 // Coroutine Scheduler: 27 // http://wiki.unity3d.com/index.PHP/Coroutinescheduler 28 // 29 // JavaScript yield documents: 30 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield 31 // 32 33 // fiber 类似于 C# 的 IEnumerator 34 _jstype.deFinition.$AddCoroutine = function (fiber) { 35 var coroutineNode = { 36 $__CN: true, // mark this is a coroutine node 37 prev: undefined, 38 next: undefined, 39 fiber: fiber, 40 finished: false, 41 42 waitForFrames: 0, // yield null 43 waitForSeconds: undefined, // WaitForSeconds 44 www: undefined, // WWW 45 waitForCoroutine: undefined, // Coroutine 46 }; 47 48 if (this.$first) { 49 coroutineNode.next = this.$first; 50 this.$first.prev = coroutineNode; 51 }; 52 53 this.$first = coroutineNode; 54 // NOTE 55 // return coroutine node itself! 56 return coroutineNode; 57 } 58 59 // this method is called from LateUpdate 60 _jstype.deFinition.$UpdateallCoroutines = function (elapsed) { 61 // cn is short for Coroutine Node 62 var cn = this.$first; 63 while (cn != undefined) { 64 // store next coroutineNode before it is removed from the list 65 var next = cn.next; 66 var update = false; 67 68 if (cn.waitForFrames > 0) { 69 cn.waitForFrames--; 70 if (cn.waitForFrames <= 0) { 71 waitForFrames = 0; 72 this.$UpdateCoroutine(cn); 73 } 74 } 75 else if (cn.waitForSeconds) { 76 if (cn.waitForSeconds.get_finished(elapsed)) { 77 cn.waitForSeconds = undefined; 78 this.$UpdateCoroutine(cn); 79 } 80 } 81 else if (cn.www) { 82 if (cn.www.get_isDone()) { 83 cn.www = undefined; 84 this.$UpdateCoroutine(cn); 85 } 86 } 87 else if (cn.waitForCoroutine) { 88 if (cn.waitForCoroutine.finished == true) { 89 cn.waitForCoroutine = undefined; 90 this.$UpdateCoroutine(cn); 91 } 92 } 93 else { 94 this.$UpdateCoroutine(cn); 95 } 96 cn = next; 97 } 98 } 99 100 _jstype.deFinition.$UpdateCoroutine = function (cn) { // cn is short for Coroutine Node 101 var fiber = cn.fiber; 102 var obj = fiber.next(); 103 if (!obj.done) { 104 var yieldCommand = obj.value; 105 // UnityEngine.Debug.Log$$Object(JSON.stringify(yieldCommand)); 106 if (yieldCommand == null) { 107 cn.waitForFrames = 1; 108 } 109 else { 110 if (yieldCommand instanceof UnityEngine.WaitForSeconds.ctor) { 111 cn.waitForSeconds = yieldCommand; 112 } 113 else if (yieldCommand instanceof UnityEngine.WWW.ctor) { 114 cn.www = yieldCommand; 115 } 116 else if (yieldCommand.$__CN === true/*yieldCommand.toString() == "[object Generator]"*/) { 117 cn.waitForCoroutine = yieldCommand; 118 } 119 else { 120 throw "Unexpected coroutine yield type: " + yieldCommand.GetType(); 121 } 122 } 123 } 124 else { 125 // UnityEngine.Debug.Log$$Object("cn.finished = true;"); 126 cn.finished = true; 127 this.$RemoveCoroutine(cn); 128 } 129 } 130 131 _jstype.deFinition.$RemoveCoroutine = function (cn) { // cn is short for Coroutine Node 132 if (this.$first == cn) { 133 this.$first = cn.next; 134 } 135 else { 136 if (cn.next != undefined) { 137 cn.prev.next = cn.next; 138 cn.next.prev = cn.prev; 139 } 140 else if (cn.prev) { 141 cn.prev.next = undefined; 142 } 143 } 144 cn.prev = undefined; 145 cn.next = undefined; 146 } 147 }
目前支持的 yield return 后面可接的类型有:
yield return null; // 下一帧调用 MoveNext()
yield return new WWW(...); // WWW
yield return new WaitForSeconds(...); // 等待一定时间
yield return new StartCoroutine(...); // 串连另一个协程
C# 协程和 JavaScript 协程有一个区别:C#是协程初始就调用了 MoveNext(),JavaScript 需要初始调用 next() 才能和 C# 匹配(现在没有调用,因为一帧的时间也挺快的,效果差不多一样)。
1 void LateUpdate() 2 { 3 jsimp.Coroutine.UpdateMonoBehavIoUrCoroutine(this); 4 }
现在因为我们自己要管理协程,所以需要有一个 Update 入口。如果想让 JavaScript 协程正常工作,必须在某个地方调用协程管理器的 Update。现在我是把他放在 LateUpdate 函数中,如果你想换地方,也是可以的。
在 C# 中,jsimp.Coroutine.UpdateMonoBehavIoUrCoroutine 函数并不做任何事情,只有当运行 JavaScript 版本时,才有做事情。JavaScript 的实现是在这个文件中:
StreamingAssets/JavaScript/JSImp/Coroutine.javascript
1 if (typeof(JsTypes) == "undefined") 2 var JsTypes = []; 3 var jsimp$Coroutine = { 4 fullname: "jsimp.Coroutine", 5 baseTypeName: "System.Object", 6 staticDeFinition: { 7 UpdateMonoBehavIoUrCoroutine: function (mb){ 8 mb.$UpdateallCoroutines(UnityEngine.Time.get_deltaTime()); 9 } 10 }, 11 assemblyName: "SharpKitProj", 12 Kind: "Class", 13 deFinition: { 14 ctor: function (){ 15 System.Object.ctor.call(this); 16 } 17 } 18 }; 19 20 // replace old Coroutine 21 jsb_ReplaceOrPushJsType(jsimp$Coroutine);
这个文件同样在 includes.javascript 中进行了包含。
看第8行,调用了 $UpdateallCoroutines 函数更新协程管理器。
对于 WaitForSeconds 类,C#中并没有暴露任何接口。我们无法判断一个 WaitForSeconds 是否时间已到。所以我又自定义了这个类,来达到这个目的。
文件是:StreamingAssets/JavaScript/Manual/UnityEngine_WaitForSeconds.javascript
1 _jstype = undefined; 2 for (var i = 0; i < JsTypes.length; i++) { 3 if (JsTypes[i].fullname == "UnityEngine.WaitForSeconds") { 4 _jstype = JsTypes[i]; 5 break; 6 } 7 } 8 9 if (_jstype) { 10 11 _jstype.deFinition.ctor = function(a0) { 12 this.$totalTime = a0; 13 this.$elapsedtime = 0; 14 this.$finished = false; 15 } 16 17 _jstype.deFinition.get_finished = function(elapsed) { 18 if (!this.$finished) { 19 this.$elapsedtime += elapsed; 20 if (this.$elapsedtime >= this.$totalTime) { 21 this.$finished = true; 22 } 23 } 24 return this.$finished; 25 } 26 }
这个文件也很简单,只是记录初始时的时间,后面更新时时间进行递增。get_finished() 函数被协程管理器用于判断时间是否已到。
到此,相信大家对“用C#写的协程转换成JavaScript后无法正常工作怎么办”有了更深的了解,不妨来实际操作一番吧!这里是编程之家网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!