JSBinding + SharpKit / Coroutine支持

首先得深刻了解協程的原理。若是尚未徹底理解,建議看這篇:javascript

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

另外還要對 JavaScript 的 yield 有所瞭解,能夠看 Mozilla 這篇文檔:html

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

 

---------------------------------------------------------------------------node

如下這一段過期,新版本已經沒有這個問題,請看其餘文章介紹數組

先說結論吧:用C#寫的協程轉換成 JavaScript 後,沒法正常工做,必需要手動修改一點點代碼。函數

 

源代碼中的協程例子工程:Assets/JSBinding/Samples/Coroutine/TestCoroutine.unitypost

如下是 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 編譯後的代碼:this

 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 首頁

相關文章
相關標籤/搜索