[Unity]Puerts for Unity使用筆記

記錄一下普洱TS的安裝而芥、代碼打包潦闲、調(diào)試與FairyGUI集成等垦沉,以及使用過程中遇到的問題辈讶。

基本使用

安裝

按照官方手冊,拷貝puerts/unity/Assets下的所有內(nèi)容到您項(xiàng)目的Assets目錄下犀被,在
release中下載插件并解壓覆蓋到Plugins目錄椅您,插件有不同的js引擎版本,不知道選什么的話建議用v8寡键。

Unity示例 在另一個(gè)倉庫掀泳,是獨(dú)立的Unity工程,看完里面的示例基本上就能明白大致使用方法了西轩。

Hello Kitty

按照國際慣例员舵,先來寫個(gè)Hello Kitty。

配置

如果僅安裝了Puerts藕畔,沒有拷貝示例代碼马僻,則需要在Unity中做一些準(zhǔn)備工作。為了快速看效果注服,只做一個(gè)簡單的配置韭邓,Assets下新建Editor目錄,其下新建PuertsConfig.cs:
PuertsConfig.cs

[Configure]
public class PuertsConfig
{
    [Binding]
    static IEnumerable<Type> Bindings =>
        new List<Type>()
        {
            typeof(Debug),
            typeof(Vector3),
            typeof(List<int>),
            typeof(Dictionary<string, List<int>>),
            typeof(Time),
            typeof(Transform),
            typeof(Component),
            typeof(GameObject),
            typeof(UnityEngine.Object),
            typeof(Delegate),
            typeof(System.Object),
            typeof(Type),
            typeof(ParticleSystem),
            typeof(Canvas),
            typeof(RenderMode),
            typeof(Behaviour),
            typeof(MonoBehaviour),
        };
}

執(zhí)行菜單Puerts->Generate index.d.ts:

將會(huì)生成對應(yīng)的類型聲明文件:

TyepeScript工程

在項(xiàng)目根目錄下新建一個(gè)TsProject文件夾(官方示例中為TsProj)溶弟,作為TypeScript工程目錄仍秤。

用vscode打開它,在這之前請確保已經(jīng)安裝好了vscode可很、node诗力、npm、typescript我抠,新建tsconfig.json苇本,加入如下配置:
tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "jsx": "react",
    "sourceMap": true,
    "noImplicitAny": true,
    "typeRoots": [
      "../Assets/Puerts/Typing",
      "../Assets/Gen/Typing",
      "./node_modules/@types"
    ],
    "outDir": "output"
  }
}

typeRoots中指定了C#側(cè)的類型聲明文件目錄,如果你的ts工程目錄或者Puerts目錄有變更菜拓,這里需要修改正確瓣窄。
outDir指定了編譯后js文件的輸出目錄。其他的配置沒什么好說的纳鼎,可以根據(jù)個(gè)人喜好調(diào)整俺夕,更多配置項(xiàng)說明可以查看TypeScript的官方文檔
新建package.json贱鄙,加入如下配置:
package.json

{
  "name": "tsproj",
  "version": "1.0.0",
  "description": "ts project",
  "scripts": {
    "build": "tsc -p tsconfig.json",
    "postbuild": "node copyJsFile.js output ../Assets/Resources"
  }
}

這兩個(gè)文件也可以用npm inittsc --init創(chuàng)建劝贸。
把官方示例的TsProj文件夾里的copyJsFile.js拷貝過來,新建index.ts逗宁,編寫Hello World:
index.ts

console.log('Hello Kitty!');

終端里運(yùn)行:

npm run build

可以看到output文件夾輸出了編譯后的index.js文件與map文件:

并且這些文件被拷貝到了Recources目錄下:

執(zhí)行

Scripts下新建JsManager.cs映九,編寫執(zhí)行代碼:
JsManager.cs

namespace LearnPuerts
{
    public class JsManager : MonoBehaviour
    {
        private static JsEnv jsEnv;
        private void Awake()
        {
            jsEnv ??= new JsEnv(new DefaultLoader());
            jsEnv.Eval("require('index');");
        }
        
        private void Update()
        {
            jsEnv.Tick();
        }
        
        private void OnDestroy()
        {
            jsEnv.Dispose();
        }
    }
}

腳本掛到場景中,運(yùn)行即可看到效果:

C__Users_OSoleMio_OneDrive_文檔_Blog_Puerts_hello_world.png

打包與調(diào)試

打包

在凍手之前瞎颗,先看看默認(rèn)的build都干了些什么:

首先tsc編譯件甥,文件輸出到output文件夾下捌议,然后執(zhí)行copyJsFile.js將文件拷貝到了Assets/Resources目錄下。
那么打包過程依葫蘆畫瓢即可引有,先打包瓣颅,再拷貝。官方說明中用的是webpack譬正,個(gè)人更習(xí)慣用esbuild宫补,也差不了太多。
先把esbuild裝好导帝,終端里執(zhí)行:

npm install esbuild --save-dev

拷貝過程懶得自己寫了,直接用copyJsFile.js穿铆,修改它的代碼您单,導(dǎo)出拷貝方法:
copyJsFile.js

// if (process.argv.length == 4) {
//     copyFolderRecursiveSync(process.argv[2], process.argv[3]);
// } else {
//     console.error('invalid arguments');
// }
exports.copyFolder = copyFolderRecursiveSync

新建build.js,加入相應(yīng)依賴荞雏,指定輸出目錄與拷貝目標(biāo)目錄:
build.js

var copyFolder = require('./copyJsFile').copyFolder;

var outputFolder = 'output';
var targetFolder = '../Assets/Resources';

編寫打包配置:
build.js

// https://esbuild.github.io/api/#build-api
var options = {
    bundle: true,
    entryPoints: ["index.ts"],
    incremental: true,
    minify: process.env.NODE_ENV === "production",
    outfile: outputFolder + "/bundle.js",
    platform: "node",
    tsconfig: "./tsconfig.json",
    sourcemap: process.env.NODE_ENV === "production" ? false : true,
    external: ['csharp', 'puerts', 'path', 'fs'],
    treeShaking: true,
    logLevel: 'error'
};

根據(jù)說明虐秦,csharp、puerts凤优、path悦陋、fs在打包時(shí)需要排除,其他配置可以根據(jù)個(gè)人需求調(diào)整筑辨。
同時(shí)希望打包支持watch俺驶,這樣ts代碼有改動(dòng)就能同步更新輸出文件,通過獲取命令行參數(shù)棍辕,判斷當(dāng)前是否為watch模式:
build.js

var watchMode = false;
for (let i = 2; i < process.argv.length; i++) {
    if (process.argv[i] == 'watch') {
        watchMode = true;
        break;
    }
}

如果為watch模式暮现,則增加對應(yīng)watch配置,在Rebuild時(shí)將輸出文件拷貝到目標(biāo)目錄下:

if (watchMode) {
    options.watch = {
        onRebuild(error, result) {
            if (error) {
                console.error('watch build failed:', error);
            } else {
                copyFolder(outputFolder, targetFolder);
                console.log('watch build succeeded:', result);
            }
        }
    }
} else if (process.env.NODE_ENV === "production") {
    // 正式打包時(shí)將刪除輸出目錄下所有文件
    var fs = require('fs');
    var path = require('path');
    fs.rmSync(path.dirname(options.outfile), { recursive: true, force: true })
}

最后執(zhí)行:

require('esbuild').build(options)
    .then(() => {
        copyFolder(outputFolder, targetFolder);
    })
    .then(() => {
        if (watchMode)
            console.log('??Watching...');
        else {
            console.log('??Build finished.');
            process.exit(0);
        }
    });

build.js寫完了楚昭,接下來修改package.json:

...
  "scripts": {
    "build-product": "cross-env NODE_ENV=production node build.js",
    "build": "node build.js",
    "watch": "node build.js watch"
  },
...

記得把cross-env裝一下:

npm install cross-env --save-dev 

隨便寫點(diǎn)東西栖袋,運(yùn)行npm run watchnpm run build,可以看到打好包的bundle.js:

記得修改執(zhí)行處的文件名:
JsManager.cs

private void Awake()  
{  
    jsEnv ??= new JsEnv(new DefaultLoader());  
    jsEnv.Eval("require('bundle');");  
}

調(diào)試

調(diào)試可以參考官方文檔抚太,按文檔配置一遍塘幅,Unity中運(yùn)行后,再在vscode中啟動(dòng)調(diào)試器即可尿贫。這里記錄一些我在瞎搞過程中遇到的問題电媳。

調(diào)試器連不上

檢查launch.json中的端口是否與C#代碼中的一致,并且端口未被占用庆亡,OnDestroy中需要調(diào)用jsEnv.Dispose()銷毀匆背,避免退出運(yùn)行后端口依然處于占用狀態(tài)。

斷點(diǎn)無效

斷點(diǎn)為灰色身冀,并提示“Unbound breakpoint”:

這種情況一般是source map出了問題钝尸,可以從這幾個(gè)方面檢查:

  1. tsconfig.json里有沒有開啟source map
  2. 打包代碼(build.js)里有沒有開啟source map
  3. source map文件生成了沒有
  4. source map文件中的源文件路徑是否正確(一般沒問題)
  5. C#中是否指定了正確的js輸出目錄
  6. 加載Resources子目錄下的js文件時(shí)括享,js輸出目錄要保持同樣的結(jié)構(gòu)

對于第六點(diǎn),比如js文件不是拷貝到Resources根目錄珍促,而是拷貝到Resources/tsbuild目錄中:

jsEnv.Eval("require('tsbuild/bundle');");

那么需要讓輸出目錄也保持這個(gè)結(jié)構(gòu):

不過一般不會(huì)直接從Resources下加載铃辖,用Addressable或AssetBundle的情況比較多。

如果出現(xiàn)程序運(yùn)行得太快猪叙,有些斷點(diǎn)沒進(jìn)的情況娇斩,并已使用了jsEnv的等待調(diào)試器連接,那么可以嘗試在launch.json中開啟pauseForSourceMap穴翩。

Source Map Support

在index.ts中報(bào)個(gè)異常試試:

JSON.parse('aa');

并不能追蹤到源碼的報(bào)錯(cuò)位置:

官方faq文檔中有解決方法犬第,使用source-map-support。通常只需要require之后install就行芒帕,但由于source-map-support是一個(gè)nodejs模塊歉嗓,它引用到了node的path與fs,其他js引擎中沒有這兩個(gè)模塊背蟆,所以需要按照文檔中將它們改為C# System.IO的實(shí)現(xiàn)鉴分。

如果按文檔做了一遍還是不行的話,可以嘗試修改source map文件的獲取過程带膀,在install中加入自定義的處理邏輯:

// require('source-map-support').install();
require('source-map-support').install({
    retrieveSourceMap: function (source: string) {
        if (source.endsWith('bundle.js')) {
            let mapFile = csharp.System.IO.Path.Combine(csharp.UnityEngine.Application.dataPath, '../TsProject/output/bundle.js.map');
            if (csharp.System.IO.File.Exists(mapFile)) {
                return {
                    url: source,
                    map: csharp.System.IO.File.ReadAllText(mapFile)
                };
            }
        }
        return null;
    }
});

可以追蹤到報(bào)錯(cuò)位置:

FairyGUI

FairyGUI官方有Puerts的使用說明志珍,按文檔搞就完事了。這里主要介紹一個(gè)FairyGUI Puerts插件垛叨,可以直接生成TypeScript的UI代碼伦糯,喜歡的話請給作者一個(gè)Star。
首先按官方的使用說明在Unity中安裝FairyGUI SDK嗽元,并做好相關(guān)配置舔株,然后隨便建個(gè)UI工程,目錄與Assets还棱、TsProject同級:

將插件倉庫克隆到UiProject下的plugins目錄载慈,重啟FairyGUI編輯器,可以看到新增的插件:

發(fā)布設(shè)置中設(shè)置發(fā)布路徑:

包設(shè)置中記得勾選“為本包生成代碼”:

發(fā)布即可看到生成的UI代碼:

生成的UI代碼放在發(fā)布路徑的包名文件夾下珍手,比如這里包名為DefaultPackage办铡。
然后就可以使用了:
index.ts

import { FairyGUI } from 'csharp';
import UI_Main from './src/gen/ui/DefaultPackage/UI_Main';
import { bind } from './src/gen/ui/DefaultPackage/fairygui';
// 加載包
FairyGUI.UIPackage.AddPackage('fgui/DefaultPackage');
// 繼承生成的組件類
class UIMain extends UI_Main {
    protected override onConstruct(): void {
        super.onConstruct();
        this.m_guguButton.onClick.Add(() => {
            this.m_guguText.text += '咕';
        });
    }
}
// 綁定到FairyGUI
bind(UIMain);
// 創(chuàng)建實(shí)例
let uiMain = UIMain.createInstance<UIMain>();
// 設(shè)置設(shè)計(jì)分辨率
FairyGUI.GRoot.inst.SetContentScaleFactor(800, 600);
// 添加到UI
FairyGUI.GRoot.inst.AddChild(uiMain);

這里定義一個(gè)子類UIMain繼承生成的UI_Main,在點(diǎn)擊按鈕時(shí)添加一個(gè)”咕“琳要。
運(yùn)行效果:

生成的代碼是如何工作的寡具?

fairygui.ts中提供了一個(gè)bind函數(shù),調(diào)用FairyGUI提供的API將傳入的ts類擴(kuò)展為組件稚补,并將C#側(cè)會(huì)調(diào)用的__onConstruct等方法綁定到ts類的對應(yīng)方法上:

XXXBinder.ts中將所有ts組件類綁定童叠,這里沒有用到這個(gè)類,而是手動(dòng)調(diào)用bind綁定。

UI_Main.ts的onConstruct中厦坛,獲取了所有子組件五垮,所以可以直接使用:

個(gè)人認(rèn)為createInstance中的as T有點(diǎn)可疑,畢竟ts中的as只是類型斷言杜秸,不像C#中有類型轉(zhuǎn)換的功能放仗,這里僅起到類型檢查的作用。實(shí)際測試中撬碟,如果bind父類UI_Main而非子類UIMain诞挨,UIMain.createInstance實(shí)際返回的依然是父類UI_Main的對象,自然也不會(huì)執(zhí)行子類的方法呢蛤』躺担總之如果有擴(kuò)展子類,那么記得手動(dòng)bind一下子類其障。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末银室,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子静秆,更是在濱河造成了極大的恐慌粮揉,老刑警劉巖巡李,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抚笔,死亡現(xiàn)場離奇詭異,居然都是意外死亡侨拦,警方通過查閱死者的電腦和手機(jī)殊橙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狱从,“玉大人膨蛮,你說我怎么就攤上這事〖狙校” “怎么了敞葛?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長与涡。 經(jīng)常有香客問我惹谐,道長,這世上最難降的妖魔是什么驼卖? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任氨肌,我火速辦了婚禮,結(jié)果婚禮上酌畜,老公的妹妹穿的比我還像新娘怎囚。我一直安慰自己,他們只是感情好桥胞,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布恳守。 她就那樣靜靜地躺著考婴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪井誉。 梳的紋絲不亂的頭發(fā)上蕉扮,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音颗圣,去河邊找鬼喳钟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛在岂,可吹牛的內(nèi)容都是我干的奔则。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼蔽午,長吁一口氣:“原來是場噩夢啊……” “哼易茬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起及老,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤抽莱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后骄恶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體食铐,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年僧鲁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了虐呻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡寞秃,死狀恐怖斟叼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情春寿,我是刑警寧澤朗涩,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站绑改,受9級特大地震影響谢床,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绢淀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一萤悴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧皆的,春花似錦覆履、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽栖雾。三九已至,卻和暖如春伟众,著一層夾襖步出監(jiān)牢的瞬間析藕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工凳厢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留账胧,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓先紫,卻偏偏與公主長得像治泥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子遮精,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容