• Libgdx游戏开发(4)——显示中文文字


    本文为作者原创,允许转载,不过请在文章开头明显处注明链接和出处!!! 谢谢配合~
    作者:stars-one
    链接:https://www.cnblogs.com/stars-one/p/18254460

    本篇大约有9572个字,阅读预计需要11.96分钟


    原文: Libgdx游戏开发(4)——显示中文文字-Stars-One的杂货小窝

    本文代码示例采用kotlin代码进行讲解,且需要有libgdx入门基础

    这里主要介绍关于在Libgdx显示文字的2种方法

    2种方法优缺点

    BitmapFont

    优势:

    1. 易于操作和使用,简单快速实现文本渲染。
    2. 资源消耗相对较低,速度较快。
    3. 支持利用工具生成位图字体,可以实现自定义字体样式。

    缺点:

    1. 缩放时可能导致文字变得模糊。
    2. 不支持平滑缩放,容易出现锯齿效果。
    3. 需要手动创建位图字体文件,可能需要额外的工作量。

    FreeTypeFont

    优势:

    1. 支持平滑缩放和旋转,渲染效果更加平滑、清晰。
    2. 支持各种字体格式,更加灵活。
    3. 可以在运行时动态加载字体文件。

    缺点:

    1. 资源消耗相对较高,速度较BitmapFont略慢。
    2. 对于大型字体或文本,内存占用可能较高。
    3. 需要额外的库(FreeType)支持,增加了依赖

    方法1 使用BitmapFont

    1.下载hiero工具

    Hiero - libGDX页面中下载hiero工具,如下图

    下载下来的是一个jar文件,通过java -jar命令打开即可(我自己用的是JDK8,看软件页面应该是使用的swing相关组件,高版本java环境应该也是能够打开)

    软件界面下图所示

    2.生成fnt文件

    我们需要使用hiero工具生成一个fnt文件(从翻译上来说是一个字体的位图文件,即每个字符都是一个位图)

    左侧我们可以选择系统ttf字体或者从文件中选择一个ttf文件来生成(以及进行一些字体大小,加粗或斜体设置)

    可以看到渲染方式有三种,具体官方解释如下:

    • FreeType: 通常质量最高。它充分利用了提示,这意味着小字体可以很好地呈现。该gamma设置控制抗锯齿程度。该mono设置禁用所有字体平滑。不支持其他效果,但可以使用填充和通过 Photoshop 或其他工具应用的效果来渲染字形。Hiero 使用 gdx-freetype,因此生成的位图字体将与 gdx-freetype 即时渲染的字体完全匹配。
    • Java: 此字体渲染为字形提供了矢量轮廓,允许应用各种效果,例如阴影、轮廓等。小尺寸的输出通常很模糊,但大尺寸的输出质量很好。
    • Natvie: 操作系统原生渲染最为简单。它不提供紧密贴合的边界,因此字形会占用更多的图集空间。

    在右边的输入框中,输入相关文字,点击file菜单栏即可生成fnt文件,如下图所示

    有大佬提及使用过程中会有失真的过程,可以看着调整page width和page height属性,调大些

    可以使用下面的3000个汉字来生成fnt文件

    常用3000个汉字,按常用顺序分段(每段1000个字)

    的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把性好应开它合还因由其些然前外天政四日那社义事平形相全表间样与关各重新线内数正心反你明看原又么利比或但质气第向道命此变条只没结解问意建月公无系军很情者最立代想已通并提直题党程展五果料象员革位入常文总次品式活设及管特件长求老头基资边流路级少图山统接知较长将组见计别她手角期根论运农指几九区强放决西被干做必战先回则任取据处队南给色光门即保治北造百规热领七海地口东导器压志世金增争济阶油思术极交受联什认六共权收证改清已美再采转更单风切打白教速花带安场身车例真务具万每目至达走积示议声报斗完类八离华名确才科张信马节话米整空元况今集温传土许步群广石记需段研界拉林律叫且究观越织装影算低持音众书布复容儿须际商非验连断深难近矿千周委素技备半办青省列习响约支般史感劳便团往酸历市克何除消构府称太准精值号率族维划选标写存候毛亲快效斯院查江型眼王按格养易置派层片始却专状育厂京识适属圆包火住调满县局照参红细引听该铁价严首底液官德调随病苏失尔死讲配女黄推显谈罪神艺呢席含企望密批营项防举球英氧势告李台落木帮轮破亚师围注远字材排供河态封另施减树溶怎止案言士均武固叶鱼波视仅费紧爱左章早朝害续轻服试食充兵源判护司足某练差致板田降黑犯负击范继兴似余坚曲输修的故城夫够送笔船占右财吃富春职觉汉画功巴跟虽杂飞检吸助升阳互初创抗考投坏策古径换未跑留钢曾端责站简述钱副尽帝射草冲承独令限阿宣环双请超微让控州良轴找否纪益依优顶础载倒房突坐粉敌略客袁冷胜绝析块剂测丝协重诉念陈仍罗盐友洋错苦夜刑移频逐靠混母短皮终聚汽村云哪既距卫停烈央察烧行迅境若印洲刻括激孔搞甚室待核校散侵吧甲游久菜味旧模湖货损预阻毫普稳乙妈植息扩银语挥酒守拿序纸医缺雨吗针刘啊急唱误训愿审附获茶鲜粮斤孩脱硫肥善龙演父渐血欢械掌歌沙著刚攻谓盾讨晚粒乱燃矛乎杀药宁鲁贵钟煤读班伯香介迫句丰培握兰担弦蛋束耐剧玉赵跳哥季课凯胡额款绍卷齐伟蒸殖永宗苗川炉岩弱零杨奏沿露杆探滑镇饭浓航怀赶库夺伊灵税了途灭赛归召鼓播盘裁险康唯录菌纯借糖盖横符私努堂域枪润幅哈竟熟虫泽脑壤碳欧遍侧寨敢彻虑斜薄庭都纳弹饲伸折麦湿暗荷瓦塞床筑恶户访塔奇透梁刀旋迹卡氯遇份毒泥退洗摆灰彩卖耗夏择忙铜献硬

    予繁圈雪函亦抽篇阵阴丁尺追堆雄迎泛爸楼避谋吨野猪旗累偏典馆索秦脂潮爷豆忽托惊塑遗愈朱替纤粗倾尚痛楚谢奋购磨君池旁碎骨监捕弟暴割贯殊释词亡壁顿宝午尘闻揭炮残冬桥妇警综招吴付浮遭徐您摇谷赞箱隔订男吹乐园纷唐败宋玻巨耕坦荣闭湾键凡驻锅救恩剥凝碱齿截炼麻纺禁废盛版缓净睛昌婚涉筒嘴插岸朗庄街藏姑贸腐奴啦惯乘伙恢匀纱扎辩耳彪臣亿璃抵脉秀萨俄网舞店喷纵寸汗挂洪着贺闪柬爆烯津稻墙软勇像滚厘蒙芳肯坡柱荡腿仪旅尾轧冰贡登黎削钻勒逃障氨郭峰币港伏轨亩毕擦莫刺浪秘援株健售股岛甘泡睡童铸汤阀休汇舍牧绕炸哲磷绩朋淡尖启陷柴呈徒颜泪稍忘泵蓝拖洞授镜辛壮锋贫虚弯摩泰幼廷尊窗纲弄隶疑氏宫姐震瑞怪尤琴循描膜违夹腰缘珠穷森枝竹沟催绳忆邦剩幸浆栏拥牙贮礼滤钠纹弹罢拍咱喊袖埃勤罚焦潜伍墨欲缝姓刊饱仿奖铝鬼丽跨默挖链扫喝袋炭污幕诸弧励梅奶洁灾舟鉴苯讼抱毁率懂寒智埔寄届跃渡挑丹艰贝碰拔爹戴码梦芽熔赤渔哭敬颗奔藏铅熟仲虎稀妹乏珍申桌遵允隆螺仓魏锐晓氮兼隐碍赫拨忠肃缸牵抢博巧壳兄杜讯诚碧祥柯页巡矩悲灌龄伦票寻桂铺圣恐恰郑趣抬荒腾贴柔滴猛阔辆妻填撤储签闹扰紫砂递戏吊陶伐喂疗瓶婆抚臂摸忍虾蜡邻胸巩挤偶弃槽劲乳邓吉仁烂砖租乌舰伴瓜浅丙暂燥橡柳迷暖牌纤秧胆详簧踏瓷谱呆宾糊洛辉愤竞隙怒粘乃绪肩籍敏涂熙皆侦悬掘享纠醒狂锁淀恨牲霸爬赏逆玩陵祝秒浙貌役彼悉鸭着趋凤晨畜辈秩卵署梯炎滩棋驱筛峡冒啥寿译浸泉帽迟硅疆贷漏稿冠嫩胁芯牢叛蚀奥鸣岭羊凭串塘绘酵融盆锡庙筹冻辅摄袭筋拒僚旱钾鸟漆沈眉疏添棒穗硝韩逼扭侨凉挺碗栽炒杯患馏劝豪辽勃鸿旦吏拜狗埋辊掩饮搬骂辞勾扣估蒋绒雾丈朵姆拟宇辑陕雕偿蓄崇剪倡厅咬驶薯刷斥番赋奉佛浇漫曼扇钙桃扶仔返俗亏腔鞋棱覆框悄叔撞骗勘旺沸孤粘吐孟渠屈疾妙惜仰狠胀谐抛霉桑岗嘛衰盗渗脏赖涌甜曹阅肌哩厉烃纬毅昨伪症煮叹钉搭茎笼酷偷弓锥恒杰坑鼻翼纶叙狱逮罐络棚抑膨蔬寺骤穆冶枯册尸凸绅坯牺焰轰欣晋瘦御锭锦丧旬锻垄搜佛扑邀亭酯迈舒脆酶闲忧酚顽羽涨卸仗陪薄辟惩杭姚肚捉飘漂昆欺吾郎烷汁呵饰萧雅邮迁燕撒姻赴宴烦削债帐斑铃旨醇董饼雏姿拌傅腹妥揉贤拆歪葡胺丢浩徽昂垫挡览贪慰缴汪慌冯诺姜谊凶劣诬耀昏躺盈骑乔溪丛卢抹易闷咨刮驾缆悟摘铒掷颇幻柄惠惨佳仇腊窝涤剑瞧堡泼葱罩霍捞胎苍滨俩捅湘砍霞邵萄疯淮遂熊粪烘宿档戈驳嫂裕徙箭捐肠撑晒辨殿莲摊搅酱屏疫哀蔡堵沫皱畅叠阁莱敲辖钩痕坝巷饿祸丘玄溜曰逻彭尝卿妨艇吞韦怨矮歇郊禄捻漠粹颠宏冤肪饥呵仙押挨醛娃拾没佩勿吓

    讹侯恋夕锌篡戚淋蓬岂釉兆泊魂拘亡杠摧氟颂浑凌铀诱犁谴颁舶扯嘉萌犹滋焊舌匹媳肺掠酿烹疲驰鸦窄辱狭朴遣菲奸韧辣拳秆卧醉竭茅墓矣哎艳敦舆缔雇尿葬履契禽渣衬躲赔咸溉贼醋堤抖妃裤廉晴挽掀茫丑亥拦悠阐慧佐奇竖孝柜麟绣遥逝愁肖昭芬逢窑捷圜盲闸宙辐披账狼幽绸蜂慎餐酬誓惟叉弥址帜芝砌唉仆涛臭翠盒劫慨炳阖寂椒倘拓畏喉巾颈垦拚兽蔽芦乾爽窃谭挣崩模褐传翅儒伞晃谬胚剖凑眠浊霜礁蔑抄闯洒碑蓉耶猜蹲壶唤澳锯郡玲绵纽梳掏吁锤鼠穴椅殷遮吵萍厌畜俱夸吕囊捧雌闽饶瞬郁哨凿朝俺浒茂肝勋盯籽耻菊滥稼戒奈帅鞭蚕镁询跌烤坛宅笛鄂蛮颤棍睁鼎岌降侍藩嚷匪岳糟缠迪泄卑氛堪萝盛碘缚悦澄甫攀屠溢拱晰携朽吟菱谦凹俊芒盼婶艘酰趁唇挫羞浴疼萎肴愚肿刨绞枢嫁慕舱铲苹豫谕迭潘顷翁榜匠欠茬畴胃沾踪弊哼鹏歧桐沃悼惑溃蔗荐潭孢露诊庸聪嫌厨庞祁钳肆梭赠崖篮颖甸藻捣且撕诏贞赐慈炕胖兹差琼锈汛卓棵馈挠灶婴蒂肤衫沥仑勉沪逸蜜浦嗓晕膏祭赢艾扮鹅怜蒲兔孕呖蘖挪淑谣惧廊缅俘骄膀陡宰诞峻恼腺猎涡夷愉魔铵葛贾似荫哟脊钞苛锰椭镶杏溴倚滞会氓捏斩傲匆僵卤烫衍榨拢裸屑咽坊舅渴翔邪拄窖猫砌钦媒脾勺柏栅噪昼耿扁辰秤得贩糕梁昙衷宦扔哇诈嘱藤卜冈悔廓皂拐氰杉玛矢寓瓣罕垮笋淘衔称恭喇帕桉秉帘铭蛇摔斋叭帆裸俭瘤篷砸肢辟脖瞪暑卜竿歼笙酮蕴哗瞎喀刃楔喘枚嵌挝厢粤甩拴膝恳腕娓熄锚忌愧哦荆圃骚丸蒜毯弗俯鹿梢屯衙轿贱垒谅踢哑滔渥饷泳棕熬搁腈梨吻樱奠捆姨柏聘惕郓绑冀裹酥寡彦稠啡钝汝擅汰鳙埔敞嘿逊栋谨咖鲤雀佣庵葫贿鳞拼搏谎塌忉腻戊怖坟禾刹嘻桔坎拇煽狮痒曾梗寇鹰烛哄莽雯胳龟亟糠泌坪傻什喻渊蚌跪巷涅钊譬蕊膛侮奕枕辫况扼郝寥凄厦腥钧耦蹄戥屁诵匈桩钓涵倦袍抒屿蹈忿敷虹聊嗣尉灿糙蹬嗯姬狡笨辜僧茨讽翰枉岐枣崭焚咕猴揽涝耍趟汹咋傍镀给爵虏劈璋踩瞅迄昔汞呱诡魄祺嘲惶赃癌咐歉扳鄙庐聂便芡躯贬煌拧隋襄淤宠炊滇謇懒栓佑憾骆裙猖兜孵痼盥曝泣絮韵眷旷噢参栖盏鳌溅煎校榴暮琪淆陛巢哒吼槐唧其沛乞蜀蜇赚捍铰幂尧咒耽叮褂焕煞雹搓釜铬拣募淹瑰鲢茄灼邹躬觉娇焉彰鹤琳沦畔惹庶毙皖邢禹渍绷窜翘淫箪陌膊鞑咳玫巫拂蕉澜赎绥锄囱赌颊缕寅躁稚庚苟氦魁珊蜕蛭酌逗闺蔓撇豌朕缉襟镍桅荧侄卒佃瞿娶饪耸乍靶痴靖扛筐韶嚣崔蓿岔氘娥剿霖喃搪雍裳撰豹骏慷

    3.代码使用

    package com.arthurlumertz.taplixic
    
    import com.badlogic.gdx.ApplicationAdapter
    import com.badlogic.gdx.ApplicationListener
    import com.badlogic.gdx.Gdx
    import com.badlogic.gdx.graphics.GL20
    import com.badlogic.gdx.graphics.g2d.BitmapFont
    import com.badlogic.gdx.graphics.g2d.SpriteBatch
    
    class BitmapFontTest : ApplicationAdapter() {
        lateinit var batch: SpriteBatch
        lateinit var font: BitmapFont
    
        override fun create() {
    		//读取fnt和png文件生成BitmapFont对象
            font = BitmapFont(Gdx.files.internal("testfont/myfont.fnt"), Gdx.files.internal("testfont/myfont.png"), false)
            font!!.setColor(0.5f, 0.4f, 0.6f, 1f) // 设置颜色
    
            batch = SpriteBatch()
        }
        
        override fun render() {
            Gdx.gl.glClearColor(1f, 1f, 1f, 1f)
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
    
    		//绘制操作
            batch!!.begin()
    		//这里可以采用\n实现换行效果,
            font!!.draw(batch, "我的\n下一行", 200f, 160f)
            batch!!.end()
        }
    
        override fun dispose() {
    		//资源释放
            batch!!.dispose()
            font!!.dispose()
        }
    }
    

    需要注意:

    1. 绘制文字需要考虑y坐标
    2. 文字必须是你之前在工具里输入里的文字,如果不是的话,显示就会出现口这种特殊字符
    3. 使用\n可以实现换行效果

    比如你设置要在(0,0)处绘制文字,文字底部就会被遮挡了,如下图效果

    测试效果

    启动游戏的代码,不用多解释了

    package com.arthurlumertz.taplixic;
    
    import com.badlogic.gdx.backends.lwjgl3.*;
    
    public class DesktopLauncher {
    
    	public static void main (String[] arg) {
    		Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration();
    		config.setWindowedMode(960, 540);
    		config.setForegroundFPS(60);
    		new Lwjgl3Application(new BitmapFontTest(), config);
    	}
    
    }
    

    上图显示口这种字符就是我的生成fnt文件里没有定义的文字

    方法2 使用FreeType

    此方法最终也是使用上述的BitmapFont对象来进行绘制

    只是使用FreeTypeFontGenerator类将ttf生成了BitmapFont对象,之后的使用方法与上述绘制方法一致

    我这里是去系统里随便找了个ttf字体文件来使用

    1.依赖引入

    在主工程中加入依赖:

    api "com.badlogicgames.gdx:gdx-freetype:$gdxVersion"
    

    对应在desktop平台的build.gradle加入依赖

    api "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop"
    

    如果是Android平台,则加入下面依赖:

    api "com.badlogicgames.gdx:gdx-freetype:$gdxVersion"
            natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-armeabi-v7a"
            natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-arm64-v8a"
            natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-x86"
            natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-x86_64"
    

    如果你之前创建libgdx项目的时候勾选了FreeType,就会自动生成对应的依赖了

    2.代码使用

    package com.arthurlumertz.taplixic
    
    import com.badlogic.gdx.ApplicationAdapter
    import com.badlogic.gdx.ApplicationListener
    import com.badlogic.gdx.Gdx
    import com.badlogic.gdx.graphics.g2d.BitmapFont
    import com.badlogic.gdx.graphics.g2d.SpriteBatch
    import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator
    import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter
    
    class FreetypeFontTest : ApplicationAdapter() {
        lateinit var batch: SpriteBatch
    
        // 系统字体
        lateinit var systemFont: BitmapFont
    
        lateinit var generator: FreeTypeFontGenerator
    
        override fun create() {
            batch = SpriteBatch()
    		//自己找的一个ttf文件
            generator = FreeTypeFontGenerator(Gdx.files.internal("testfont/cnfont.ttf"))
    
            // 字体参数设置
            val parameter = FreeTypeFontParameter()
            parameter.size = 18
    
    		//需要使用的文字,里面不能有相同
            //parameter.characters = FreeTypeFontGenerator.DEFAULT_CHARS + "你好我的" // 默认字体需要提前设置好字符
    		
    		//chinese.txt文件内容里是我上面提到的三千个汉字文本
            parameter.characters = FreeTypeFontGenerator.DEFAULT_CHARS + Gdx.files.internal("testfont/chinese.txt").readString()
    
    		//生成BitmapFont对象
            systemFont = generator!!.generateFont(parameter)
        }
    
    
        override fun render() {
            batch!!.begin()
    
            systemFont!!.draw(batch, "你好的我的,这是一个数据哈哈\n下一行数据", 0f, 300f)
    
            batch!!.end()
        }
    
        override fun dispose() {
            batch!!.dispose()
            systemFont!!.dispose()
            generator!!.dispose()
        }
    }
    

    注意点:

    parameter.characters设置的字符不能有重复,因为代码逻辑是要根据字符作为key找对应的index(个人的简单理解)

    比如说你要展示文本: '今天天气真好',你的parameter.characters应该设置为今天气真好

    上面例子我是直接用了三千的文字进行生成,但这样做法会消耗很大内存,官方不太推荐

    看官方说用到什么文字就创建什么文字,但这样来不是会麻烦?还得处理下将重复字给过滤掉?这里我暂且持保留意见吧

    补充FreeTypeFontParameter可设置属性:

    /** The size in pixels */
    public int size = 16;
    /** Foreground color (required for non-black borders) */
    public Color color = Color.WHITE;
    /** Border width in pixels, 0 to disable */
    public float borderWidth = 0;
    /** Border color; only used if borderWidth > 0 */
    public Color borderColor = Color.BLACK;
    /** true for straight (mitered), false for rounded borders */
    public boolean borderStraight = false;
    /** Offset of text shadow on X axis in pixels, 0 to disable */
    public int shadowOffsetX = 0;
    /** Offset of text shadow on Y axis in pixels, 0 to disable */
    public int shadowOffsetY = 0;
    /** Shadow color; only used if shadowOffset > 0 */
    public Color shadowColor = new Color(0, 0, 0, 0.75f);
    /** The characters the font should contain */
    public String characters = DEFAULT_CHARS;
    /** Whether the font should include kerning */
    public boolean kerning = true;
    /** The optional PixmapPacker to use */
    public PixmapPacker packer = null;
    /** Whether to flip the font vertically */
    public boolean flip = false;
    /** Whether or not to generate mip maps for the resulting texture */
    public boolean genMipMaps = false;
    /** Minification filter */
    public TextureFilter minFilter = TextureFilter.Nearest;
    /** Magnification filter */
    public TextureFilter magFilter = TextureFilter.Nearest;
    

    测试效果

    启动游戏的代码,应该不用多解释了

    package com.arthurlumertz.taplixic;
    
    import com.badlogic.gdx.backends.lwjgl3.*;
    
    public class DesktopLauncher {
    
    	public static void main (String[] arg) {
    		Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration();
    		config.setWindowedMode(960, 540);
    		config.setForegroundFPS(60);
    		new Lwjgl3Application(new FreetypeFontTest(), config);
    	}
    }
    

    使用疑问点

    下面是一下疑问点的记录,还没有具体研究得到答案,先记录着吧,或者有路过的大佬可以在评论区解答下?

    1. FreeType每次都得生成对应字符,不能直接使用单例,一次性生成要使用的字符,之后直接使用吗?
    2. 关于语言国际化的要求应该如何实现?感觉有些偏题,应该是可以使用类似的一个资源文件来自行管理,由用户选择对应的语言就加载文本,参考杀戮尖塔的语言更改,应该是需要重启游戏才能解决,不能动态实现
    3. BitmapFont会有失真的缺点,应该如何解决优化,有无优化方法?

    参考

  • 相关阅读:
    图解用户登录验证流程,写得太好了!
    6. Design A Web Crawler
    Flink系列文档-(YY12)-窗口计算
    JAVA开发搞了一年多的大数据,究竟干了点啥
    Flutter 的缓存策略
    DECIMAL 数据处理原理浅析
    MySQL:存储过程与存储引擎
    #mysql错误01#
    SpringMVC拦截器
    CSS属性:定位属性+案例讲解:博雅互动 前端开发入门笔记(五)
  • 原文地址:https://www.cnblogs.com/stars-one/p/18254460