JSBinding + SharpKit / Coroutine支持

首先得深入了解协程的原理。如果还没有完全理解,建议看这篇:

http://wiki.unity3d.com/index.php/CoroutineScheduler

另外还要对 JavaScript 的 yield 有所了解,可以看 Mozilla 这篇文档:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield

 

先说结论吧:用C#写的协程转换成 JavaScript 后,无法正常工作,必须要手动修改一点点代码。

 

源代码中的协程例子工程:Assets/JSBinding/Samples/Coroutine/TestCoroutine.unity

以下是 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);

 

需要修改的有:

  1. 协程函数改用 function* 定义
  2. 删除 $yield 数组的定义以及协程函数最后的返回
  3. 将 $yield.push 替换为 yield 。

===================================================

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 后面可接的类型有:

  1. yield return null; // 下一帧调用 MoveNext()
  2. yield return new WWW(...); // WWW
  3. yield return new WaitForSeconds(...); // 等待一定时间
  4. yield return new StartCoroutine(...); // 串连另一个协程

 C# 协程和 JavaScript 协程有一个区别:C#是协程初始就调用了 MoveNext(),JavaScript 需要初始调用 next() 才能和 C# 匹配(现在没有调用,因为一帧的时间也挺快的,效果差不多一样)。

 

另外,看前面的 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() 函数被协程管理器用于判断时间是否已到。

 

 

整个过程差不多就是这样,有想到什么再增加吧。

 

返回:Unity代码热更新方案 JSBinding + SharpKit 首页

你可能感兴趣的:(coroutine)