欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

在C#中使用適配器Adapter模式和擴展方法解決面向?qū)ο笤O(shè)計問題記錄

 更新時間:2024年10月08日 08:56:12   作者:dax.net  
在開發(fā)基于MonoGame的游戲框架時,面臨SpriteFont和DynamicSpriteFont兼容問題,SpriteFont在內(nèi)容管道中編譯確定字號,導(dǎo)致不同字號需加載多個字體資源,本文給大家介紹在C#中使用適配器Adapter模式和擴展方法解決面向?qū)ο笤O(shè)計問題,感興趣的朋友一起看看吧

之前有陣子在業(yè)余時間拓展自己的一個游戲框架,結(jié)果在實現(xiàn)的過程中發(fā)現(xiàn)一個設(shè)計問題。這個游戲框架基于MonoGame實現(xiàn),在MonoGame中,所有的材質(zhì)渲染(Texture Rendering)都是通過SpriteBatch類來完成的。舉個例子,假如希望在屏幕的某個地方顯示一個圖片材質(zhì)(imageTexture),就在Game類的子類的Draw方法里,使用下面的代碼來繪制圖片:

protected override void Draw(GameTime gameTime)
{
    // ...
    spriteBatch.Draw(imageTexture, new Vector2(x, y), Color.White);
    // ...
}

那么如果希望在屏幕的某個地方用某個字體來顯示一個字符串,就類似地調(diào)用SpriteBatchDrawString方法來完成:

protected override void Draw(GameTime gameTime)
{
    // ...
    spriteBatch.DrawString(spriteFont, "Hello World", new Vector2(x, y), Color.White);
    // ...
}

暫時可以不用管這兩個代碼中spriteBatch對象是如何初始化的,以及DrawDrawString兩個方法的各個參數(shù)是什么意思,在本文討論的范圍中,只需要關(guān)注spriteFont這個對象即可。MonoGame使用一種叫“內(nèi)容管道”(Content Pipeline)的技術(shù),將各種資源(聲音、音樂、字體、材質(zhì)等等)編譯成xnb文件,之后,通過ContentManager類,將這些資源讀入內(nèi)存,并創(chuàng)建相應(yīng)的對象。SpriteFont就是其中一種資源(字體)對象,在GameLoad方法中,可以通過指定xnb文件名的方式,從ContentManager獲取字體信息:

private SpriteFont? spriteFont;
protected override void LoadContent()
{
    // ...
    spriteFont = Content.Load<SpriteFont>("fonts\\arial"); // Load from fonts\\arial.xnb
    // ...
}

OK,與MonoGame相關(guān)的知識就介紹這么多。接下來,就進(jìn)入具體問題。由于是做游戲開發(fā)框架,那么為了能夠更加方便地在屏幕上(確切地說是在當(dāng)前場景里)顯示字符串,我封裝了一個Label類,這個類大致如下所示:

public class Label : VisibleComponent
{
    private readonly SpriteFont _spriteFont;
    public Label(string text, SpriteFont spriteFont, Vector2 pos, Color color)
    {
        Text = text;
        _spriteFont = spriteFont;
        Position = pos;
        TextColor = color;
    }
    public string Text { get; set; }
    public Vector2 Position { get; set; }
    public Color TextColor { get; set; }
    protected override void ExecuteDraw(GameTime gameTime, SpriteBatch spriteBatch)
        => spriteBatch.DrawString(_spriteFont, Text, Position, TextColor);
}

這樣實現(xiàn)本身并沒有什么問題,但是仔細(xì)思考不難發(fā)現(xiàn),SpriteFont是從Content Pipeline讀入的字體信息,而字體信息不僅包含字體名稱,而且還包含字體大?。ㄗ痔枺⑶以赑ipeline編譯的時候就已經(jīng)確定下來了,所以,如果游戲中希望使用同一個字體的不同字號來顯示不同的字符串時,就需要加載多個SpriteFont,不僅麻煩而且耗資源,靈活度也不高。

經(jīng)過一番搜索,發(fā)現(xiàn)有一款開源的字體渲染庫:FontStashSharp,它有MonoGame的擴展,可以基于字體的不同字號,動態(tài)加載字體對象(稱之為“動態(tài)精靈字體(DynamicSpriteFont)”),然后使用MonoGame原生的SpriteBatch將字符串以指定的動態(tài)字體顯示在場景中,比如:

private readonly FontSystem _fontSystem = new();
private DynamicSpriteFont? _menuFont;
public override void Load(ContentManager contentManager)
{
    // Fonts
    _fontSystem.AddFont(File.ReadAllBytes("res/main.ttf"));
    _menuFont = _fontSystem.GetFont(30);
}
public override void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
    spriteBatch.DrawString(_menuFont, "Hello World", new Vector2(100, 100), Color.Red);
}

在上面的Draw方法中,仍然是使用了SpriteBatch.DrawString方法來顯示字符串,不同的地方是,這個DrawString方法所接受的第一個參數(shù)為DynamicSpriteFont對象,這個DynamicSpriteFont對象是第三方庫FontStashSharp提供的,它并不是標(biāo)準(zhǔn)的MonoGame里的類型,所以,這里有兩種可能:

  • DynamicSpriteFont是MonoGame中SpriteFont的子類
  • FontStashSharp使用了C#擴展方法,對SpriteBatch類型進(jìn)行了擴展,使得DrawString方法可以使用DynamicSpriteFont來繪制文本

如果是第一種可能,那問題倒也簡單,基本上自己開發(fā)的這個游戲框架可以不用修改,比如在創(chuàng)建Label實例的時候,構(gòu)造函數(shù)第二個參數(shù)直接將DynamicSpriteFont對象傳入即可。但不幸的是,這里屬于第二種情況,也就是FontStashSharp中的DynamicSpriteFontSpriteFont之間并沒有繼承關(guān)系。

現(xiàn)在總結(jié)一下,目前的現(xiàn)狀是:

  • DynamicSpriteFont并不是SpriteFont的子類
  • 兩者提供相似的能力:都能夠被SpriteBatch用來繪制文本,都能夠基于給定的文本字符串來計算繪制區(qū)域的寬度和高度(兩者都提供MeasureString方法)
  • 我希望在我的游戲框架中能夠同時使用SpriteFontDynamicSpriteFont,也就是說,我希望Label可以同時兼容SpriteFontDynamicSpriteFont的文本繪制能力

很明顯,可以使用GoF95的適配器(Adapter)模式來解決目前的問題,以滿足上述3的條件。為此,可以定義一個IFontAdapter接口,然后基于SpriteFontDynamicSpriteFont來提供兩種不同的適配器實現(xiàn),最后,讓框架里的類型(比如Label)依賴于IFontAdapter接口即可,UML類圖大致如下:

DynamicSpriteFontAdapter被實現(xiàn)在一個獨立的包(C#中的Assembly)里,這樣做的目的是防止Mfx.Core項目對FontStashSharp有直接依賴,因為Mfx.Core作為整個游戲框架的核心組件,會被不同的游戲主體或者其它組件引用,而這些組件并不需要依賴FontStashSharp。

此外,同樣可以使用C#的擴展方法特性,讓SpriteBatch可以基于IFontAdapter進(jìn)行文本繪制:

public static class SpriteBatchExtensions
{
    public static void DrawString( 
        this SpriteBatch spriteBatch, 
        IFontAdapter fontAdapter, 
        string text) => fontAdapter.DrawString(spriteBatch, text);
}

 其它相關(guān)代碼類似如下:

public interface IFontAdapter
{
    void DrawString(SpriteBatch spriteBatch, string text);
    Vector2 MeasureString(string text);
}
public sealed class SpriteFontAdapter(SpriteFont spriteFont) : IFontAdapter
{
    public Vector2 MeasureString(string text) => spriteFont.MeasureString(text);
    public void DrawString(SpriteBatch spriteBatch, string text)
        => spriteBatch.DrawString(spriteFont, text);
}
public sealed class FontStashSharpAdapter(DynamicSpriteFont spriteFont) : IFontAdapter
{
    public void DrawString(SpriteBatch spriteBatch, string text)
        => spriteBatch.DrawString(spriteFont, text);
    public Vector2 MeasureString(string text) => spriteFont.MeasureString(text);
}
public class Label(string text, IFontAdapter fontAdapter) : VisibleComponent
{
    // 其它成員忽略
    public string Text { get; set; } = text;
    protected override void ExecuteDraw(GameTime gameTime, SpriteBatch spriteBatch)
        => spriteBatch.DrawString(fontAdapter, Text);
}

總結(jié)一下:本文通過對一個實際案例的分析,討論了GoF95設(shè)計模式中的Adapter模式在實際項目中的應(yīng)用,展示了如何使用面向?qū)ο笤O(shè)計模式來解決實際問題的方法。Adapter模式的引入也會產(chǎn)生一些邊界效應(yīng),比如本案例中FontStashSharp的DynamicSpriteFont其實還能夠提供更多更為豐富的功能特性,然而Adapter模式的使用,使得這些功能特性不能被自制的游戲框架充分使用(因為接口統(tǒng)一,而標(biāo)準(zhǔn)的SpriteFont并不提供這些功能),一種有效的解決方案是,擴展IAdapter接口的職責(zé),然后使用空對象模式來補全某個適配器中不被支持的功能特性,但這種做法又會在框架設(shè)計中,讓某些類型的層次結(jié)構(gòu)設(shè)計變得特殊化,也就是為了迎合某個外部框架而去做抽象,使得設(shè)計變得不那么純粹,所以,還是需要根據(jù)實際項目的需求來決定設(shè)計的方式。

到此這篇關(guān)于在C#中使用適配器Adapter模式和擴展方法解決面向?qū)ο笤O(shè)計問題記錄的文章就介紹到這了,更多相關(guān)C#面向?qū)ο笤O(shè)計內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 利用C#實現(xiàn)獲取當(dāng)前設(shè)備硬件信息

    利用C#實現(xiàn)獲取當(dāng)前設(shè)備硬件信息

    這篇文章主要為大家詳細(xì)介紹了如何利用C#實現(xiàn)獲取當(dāng)前設(shè)備硬件信息的功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下
    2023-03-03
  • WPF實現(xiàn)在線預(yù)覽和顯示W(wǎng)ord和PDF文件

    WPF實現(xiàn)在線預(yù)覽和顯示W(wǎng)ord和PDF文件

    這篇文章主要為大家詳細(xì)介紹了如何使用WPF實現(xiàn)在線預(yù)覽和顯示W(wǎng)ord和PDF文件,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-02-02
  • asp.net core項目mvc權(quán)限控制:分配權(quán)限

    asp.net core項目mvc權(quán)限控制:分配權(quán)限

    學(xué)習(xí)的最好方法就是動手去做,這里以開發(fā)一個普通的權(quán)限管理系統(tǒng)的方式來從零體驗和學(xué)習(xí)Asp.net Core。項目的整體規(guī)劃大致如下
    2017-02-02
  • C#中寫入和讀取TXT文件問題

    C#中寫入和讀取TXT文件問題

    這篇文章主要介紹了C#中寫入和讀取TXT文件問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • 關(guān)于C# 類和對象詳情

    關(guān)于C# 類和對象詳情

    類是C#面向?qū)ο缶幊痰幕締卧R粋€類都可以包含2種成員:字段和方法,接下來小編將在文章里向大家詳細(xì)介紹
    2021-09-09
  • C#與Java的MD5簡單驗證(實例代碼)

    C#與Java的MD5簡單驗證(實例代碼)

    下面小編就為大家?guī)硪黄狢#與Java的MD5簡單驗證(實例代碼)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-09-09
  • C#制作鷹眼的詳細(xì)全過程(帶注釋)實例代碼

    C#制作鷹眼的詳細(xì)全過程(帶注釋)實例代碼

    C#制作鷹眼的詳細(xì)全過程(帶注釋)實例代碼,需要的朋友可以參考一下
    2013-03-03
  • C#_SqlDependency的使用詳解

    C#_SqlDependency的使用詳解

    SqlDependency對象表示應(yīng)用程序和 SQL Server 實例間的查詢通知依賴關(guān)系,這篇文章主要介紹了C#_SqlDependency的使用,需要的朋友可以參考下
    2023-06-06
  • Unity的IPostBuildPlayerScriptDLLs實用案例深入解析

    Unity的IPostBuildPlayerScriptDLLs實用案例深入解析

    這篇文章主要為大家介紹了Unity的IPostBuildPlayerScriptDLLs實用案例深入解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • C#畫圓角矩形的方法

    C#畫圓角矩形的方法

    這篇文章主要介紹了C#畫圓角矩形的方法,涉及C#繪圖的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-05-05

最新評論