嗚嗚喔學習筆記

搜尋此網誌

2020年5月12日 星期二

C# Dependency Injection 依賴注入

程式碼常常改來改去的,但怎麼樣在需求改變的時候,只改動最少的東西捏??
這邊介紹一下 "Dependency Injection 依賴注入" 的技巧~~

舉個例子搭車好了
有一個人搭賓士車

public class Human
{
    Benz _c;
    public Human()
    {
        _c = new Benz();
        ...
    }
}

因為這裡 Human 直接參與了 new Benz() (賓士) 的創建過程,所以如果Benz創建過程有改的話 Human也要改,hen麻煩。

現實生活上也不會發生,反正人搭車,人又不是汽車製造商,今天汽車換零件。乾我屁事XD?
所以我們把車改成 "參數" 的方式丟進去就好了~

public class Human
{    
    Benz _c;
    public Human(Benz  b)
    {
       _c = b;
    }
}

ok改成這樣之後 怎麼做汽車跟我就無關咯,反正我只是要搭而已。

有天又想改搭Ferrari(法拉利)了,結果又要改Human。hen麻煩又要改?
不用我們把剛剛的參數 改成 ICar 好了,反正有車搭就行了。( 抽象層次往上提升 )

public interface ICar
{
}

public class Benz : ICar
{
}

public class Ferrai : ICar
{
}

public class Human
{
    ICar_c;
    public Human(ICar c)
    {
        _c = c;
    }
}

好啦改成這樣之後以後要什麼車都可以直接 "依賴注入" 進 human 裡面拉~
這裡就用到物件導向 "多形" 的好處了。
比如 
new Human( new Benz() );
new Human( new Ferrai() );

但其實也沒人規定一定要是汽車吧 , 可以是火車,公車,三輪車有的沒的,只要是交通工具(ITransportaion)就行了
所以也可以改成 ( 抽象程度再提升一階 )

public interface ITransportaion
{

}

public interface ICar : ITransportaion
{


public class Human
{
    ITransportaion _c;
    public Human(ITransportaion c)
    {
        _c = c;
    }
}

好啦改完之後,以後要換成火車也沒問題啦~
這就是一步一步解耦合
從搭個車還要管車子怎麼生產的,到想改搭飛機都行,反正不關我的事,只要能到目的地就好。


上面的例子,我們盡可能地讓各個Class的關聯性不要那麼高
所以最後我們抽象出 ITransportaion 讓 Human 只依賴 ITransportaion 這個介面達到更好的擴充性。

另外 Dependency Injection 對於 "抽象層次" 有很重要的關係,抽象層次越高,擴充性就越好。
如果單單只是用 Dependency Injection 其實意義並不大的感覺。

另外 Dependency Injection 也可以方便測試。
在測試 Human 類別時。
還要創建一台車,往往粉麻煩(取決於創建複雜度)(可能還有環境搭建之類的)

但我們抽離出 ITransportaion 之後做假的測試資料就很方便拉~

2020年5月5日 星期二

Unity Get Sprite Index of Texture , Unity 查找在Texture中的Sprite Index


using System; using System.Linq; using UnityEditor; using UnityEngine;


public static class SpriteUtility

{

public static byte GetSpriteIndex(Sprite sp)

{

if (sp)

{

string spriteSheet = AssetDatabase.GetAssetPath(sp.texture);

Sprite[] sprites = AssetDatabase.LoadAllAssetsAtPath(spriteSheet)

.OfType<Sprite>().ToArray();


int index = Array.FindIndex(sprites, s => s.name == sp.name);

return (byte)index;

}

else

{

return 0;

}

}

}


2020年4月24日 星期五

Unity Serialization by Json or Binary

對於Unity 序列化 & 反序列化 大致上有的選擇有 Json , Binary , YAML
YAML屬於Unity Prefab 儲存格式沒啥問題先不討論。


Json 我選用 LitJSON
Binary 選用 BinaryFormatter

總之因為這兩個都沒對 Unity 的特定資料結構處理 比如Vector2 Vector3 之類的所以使用上會出現下面的問題


Json 出現的問題 : 


JsonException: Max allowed object depth reached while trying to export from type UnityEngine.Vector2

Binary 出現問題 : 


UnityEngine.Vector2' in Assembly 'UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.

經過Google一番,總之就是新增定義Unity專用的序列方法,總之我把他解決了並且放在GitHub上。
連結在這 :


使用範例 : 


using EasyButtons; using LitJson; using System; using System.IO; using UnityEngine; [Serializable] public class Test { public Vector3 Vec3 = new Vector3(1f, 2f, 3f); public Vector2 Vec2 = new Vector2(4f, 5f); } public class Example : MonoBehaviour { [Button] public void DataToJson() { string path = Path.Combine(Application.dataPath, "Test.json"); string json = JsonMapper.ToPrettyJson(new Test()); File.WriteAllText(path, json); } [Button] public void JsonToData() { string path = Path.Combine(Application.dataPath, "Test.json"); string json = File.ReadAllText(path); Test test = JsonMapper.ToObject<Test>(json); Debug.LogFormat("vec2{0} vec3 {1}", test.Vec2, test.Vec3); } [Button] public void DataToBytes() { string path = Path.Combine(Application.dataPath, "Test.bytes"); byte[] b = BytesMapper.ToBytes<Test>(new Test()); File.WriteAllBytes(path, b); } [Button] public void BytesToData() { string path = Path.Combine(Application.dataPath, "Test.bytes"); byte[] b = File.ReadAllBytes(path); Test test = BytesMapper.ToObject<Test>(b); Debug.LogFormat("vec2{0} vec3 {1}", test.Vec2, test.Vec3); } }

2020年4月6日 星期一

Unity 打包單個 exe

教學 : https://unitycoder.com/blog/2019/05/09/creating-single-exe-from-unity-build-files/

總之Unity建完 exe 檔後 會付贈 dll 跟 data 資料夾 反正就不是單個 exe檔
hen麻煩

所以去下載

https://enigmaprotector.com/en/downloads.html


接下來照著圖的步驟走 * 關鍵 * Enter Output File 請指定到別的資料夾 & 檔名要跟原本的相同。







2020年1月21日 星期二

Unity StreamingAssets Android 讀取檔案的坑

Unity StreamingAssets 在 Android下的坑有兩個

1. 如果要用 AssetBundle.LoadFromFile 的 API Assetbundle 讀取路徑要改成
      
     string p = Application.dataPath + "!assets";
     AssetBundle.LoadFromFile(path + "/" + MyAssetBundle);

2. 如果是讀檔案不能直接使用 File.xxx 系列 要改成用 WWW or UnityWebRequest 
      
     var unityWebRequest = UnityWebRequest.Get(Path.Combine(Application.streamingAssetsPath, "MyFile.txt"));
     unityWebRequest.SendWebRequest();
     while (!unityWebRequest.isDone) { }
     string txt = unityWebRequest.downloadHandler.text;

2019年12月9日 星期一

Unity Sync .meta file , Unity 同步meta檔案

有時候會同個案子會開多個 UnityProject , 在互相搬移資料情況下如果meta檔案不同步
就可能造成 missing Script 等等問題 所以弄了一個.bat檔 來同步 *meta 檔案

如下 :


建議使用.bat 之前 先關閉unity 以免有什麼奇怪的狀況。
   

:: 設定你的路徑
set srcProjPath=C:\{yourSrcProj}\Assets\
set dstProjPath=C:\{yourDstProj}\Assets\

:: 將目標專案所有的 .meta 檔案隱藏的屬性取消才能覆寫 ( unity 會將 .meta 自動設定為隱藏 )
attrib +h /s /d %dstProjPath%"*.meta"
attrib -h /s /d %dstProjPath%"*.meta"

::複製所有 .meta 檔案 & 覆蓋
xcopy /s %srcProjPath%"*.meta*" %dstProjPath% /Y

2019年11月29日 星期五

Unity Ugui Text Outline Optimization [ Unity Ugui Text 描邊優化 ]

Github 位置 : https://github.com/tim12332000/UguiTextOutlineOptimization/tree/master 參考資料 : https://www.cnblogs.com/GuyaWeiren/p/9665106.html
基本上整個做法8成以上都是參考這篇去微調的 感謝大大!

為什麼要優化?
效能瓶頸:
其實UI畫面的 Vertex 數量不高一張圖為兩個3角形 ( 6 個 vertex )
文字則是每個一個字元 ( 6 個vertex ) 想想一篇文章幾百個字的情況下已經是滿滿的 vertex 了, 在加上 Ugui Outline原件會讓文字的vertex x5 倍
vertex 數量可以說是突破天際的高。
-------------------------------------------------------------------------------------------------------------------------
1. Ugui 自帶的 Outline 原件 Vertex x 5 倍 : 
Text Outline組件 , 實際他就是多畫四個文字在後面 , 偏移量設定多點就看出來了。(如圖)
 
2. Canvas 下的 vertices 數量最高不能超過 65000
超過會拋 "ArgumentException: Mesh can not have more than 65000 vertices"

以Unity的Outline來看一個字元 Outline就使用了 (3*2*5 = 30) 個 vertex , 所以最多只能放 ( 65000/30 = 2166..) 個字

解法:使用Shader做法替換
因為vertex消耗太高我們就把這部分放到 Fragment 階段實做。
使用Shader替換有個要求 DrawCall必須維持 1 
( 也有兩個 Pass的做法不建議使用。)
因為DrawCall 上升很大原因是因為 UIText很容易跟其他UI重疊是造成圖片無法Batch。
實際做法 : 
1.C#端 - 對 Vertex 範圍隨著黑框做放大 ( 讓黑框不會超出框框 )
2.Shader端 - 傳入 Outline 顏色 + Outline 寬度 &
計算 4個方向之後的底圖後再frag階段做疊加。
可以看到 Vextex減少 ( 左圖用新方法使用只用了6 vertex )

新做法之限制:
1. 需要使用同個Material 所以框的顏色設定都相同。
2. 在做半透明字體時 框的透明度也要跟著調整 , 因為他們是分開計算的。
3. 隨著Outline的寬度調整 Vertex 寬度也會跟著變 請注意和其他圖片相疊的可能。
(相疊Batched會被中斷)

推薦使用方式: 
1. 先建立好共通的 Material 讓所有風格相同的文字代入。
2. 不建議動態new Material 這段也是很消耗效能。( 盡量共用同個Material )

效能測試:
共使用三種做法
1. UGUI Outline
2. 自定義Shader + C# 腳本
3. 自定義Shader + C# 腳本 + JobSystem
三種做法 Drawcall 相等。
效能 : 2 > 1 > 3
自定義Shader + C# 腳本 + JobSystem 最慢(不考慮)







關於Jobsystem 優化 (Unity2018)
在C# Script 裡的 vertex放大的計算做成併行計算,所以可以丟進多執行緒內並行。
已下使用JobSystem實測 :
結果只能說慘@@? 因為Text的 UIVertex 數量不固定(所以不能Cache) 
不能Cache每次塞字進去就一定要 new NativeArray<UIVertex>()
這段GC量也不少 (懷疑是這個關係 反而用了JobSystem 之後更慢了 ?_? )





其他:
1.具體會不會因為把計算丟到Fragment 階段而多消耗多少效能 不是很清楚 ( 雖然估計是沒什麼差別) 主要是不太請楚這段怎麼測試?