最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

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

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

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

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

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

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

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

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);
}

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

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

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方法來(lái)顯示字符串,不同的地方是,這個(gè)DrawString方法所接受的第一個(gè)參數(shù)為DynamicSpriteFont對(duì)象,這個(gè)DynamicSpriteFont對(duì)象是第三方庫(kù)FontStashSharp提供的,它并不是標(biāo)準(zhǔn)的MonoGame里的類型,所以,這里有兩種可能:

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

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

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

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

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

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

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

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

相關(guān)文章

  • 基于C#對(duì)用戶密碼使用MD5加密與解密

    基于C#對(duì)用戶密碼使用MD5加密與解密

    C#中常涉及到對(duì)用戶密碼的加密于解密的算法,其中使用MD5加密是最常見(jiàn)的的實(shí)現(xiàn)方式。本文總結(jié)了通用的算法并結(jié)合了自己的一點(diǎn)小經(jīng)驗(yàn),分享給大家
    2015-12-12
  • c#.net多線程編程教學(xué)——線程同步

    c#.net多線程編程教學(xué)——線程同步

    c#.net多線程編程教學(xué)——線程同步...
    2007-04-04
  • C#中將dateTimePicker初始值設(shè)置為空

    C#中將dateTimePicker初始值設(shè)置為空

    本文主要介紹了C#中將dateTimePicker初始值設(shè)置為空,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • C#如何實(shí)現(xiàn)用戶名與密碼登錄

    C#如何實(shí)現(xiàn)用戶名與密碼登錄

    這篇文章主要介紹了C#如何實(shí)現(xiàn)用戶名與密碼登錄問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • WPF自定義控件實(shí)現(xiàn)ItemsControl魚(yú)眼效果

    WPF自定義控件實(shí)現(xiàn)ItemsControl魚(yú)眼效果

    這篇文章主要為大家詳細(xì)介紹了WPF如何通過(guò)自定義控件實(shí)現(xiàn)ItemsControl魚(yú)眼效果,文中的示例代碼講解詳細(xì),需要的可以參考一下
    2024-01-01
  • 快速學(xué)習(xí)c# 枚舉

    快速學(xué)習(xí)c# 枚舉

    這篇文章主要介紹了c# 枚舉的相關(guān)知識(shí),文中講解非常細(xì)致,示例代碼幫助大家學(xué)習(xí),感興趣的朋友可以了解下
    2020-06-06
  • C#職責(zé)鏈模式實(shí)例詳解

    C#職責(zé)鏈模式實(shí)例詳解

    這篇文章主要介紹了C#職責(zé)鏈模式,以實(shí)例形式完整分析了C#職責(zé)鏈模式的相關(guān)技巧與實(shí)現(xiàn)方法,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-07-07
  • C#刪除UL LI中指定標(biāo)簽里文字的方法

    C#刪除UL LI中指定標(biāo)簽里文字的方法

    這篇文章主要介紹了C#刪除UL LI中指定標(biāo)簽里文字的方法,涉及C#針對(duì)頁(yè)面HTML元素進(jìn)行正則匹配與替換的相關(guān)操作技巧,需要的朋友可以參考下
    2017-05-05
  • C#中的日期時(shí)間比較和格式化的方法

    C#中的日期時(shí)間比較和格式化的方法

    本文將介紹C#中常用的日期時(shí)間比較方法(CompareTo、Equals和比較運(yùn)算符)以及日期時(shí)間格式化方法(ToString、自定義格式字符串和標(biāo)準(zhǔn)格式),具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-10-10
  • C#使用Spire.XLS for .NET將Markdown轉(zhuǎn)換為PDF和Excel的完整指南

    C#使用Spire.XLS for .NET將Markdown轉(zhuǎn)換為PDF和Excel的完整指南

    在當(dāng)今數(shù)字化的世界中,Markdown已成為開(kāi)發(fā)者、作者和內(nèi)容創(chuàng)作者的首選標(biāo)記語(yǔ)言,然而,當(dāng)我們需要將這些內(nèi)容進(jìn)行分發(fā)、歸檔或進(jìn)行數(shù)據(jù)分析時(shí),Markdown的純文本格式便顯得力不從心了,本文將深入探討如何在C# .NET環(huán)境中將Markdown內(nèi)容轉(zhuǎn)換為PDF和Excel格式
    2025-12-12

最新評(píng)論

龙南县| 营口市| 平遥县| 张家口市| 汕尾市| 阜新市| 孝昌县| 喀什市| 万山特区| 宁陵县| 大渡口区| 彭水| 济南市| 即墨市| 株洲市| 靖西县| 沾益县| 乡城县| 安乡县| 黄梅县| 金寨县| 长兴县| 临西县| 西和县| 和平县| 太仓市| 沙河市| 丰原市| 怀化市| 婺源县| 五峰| 双桥区| 泗阳县| 宣化县| 隆子县| 南城县| 卓尼县| 长春市| 顺义区| 湘潭县| 禄丰县|