一 自定位圖文字
因為egret的位圖文字是texturemerger做的,需要多張單圖片導入tm,然后導出兩個文件來使用,過程比較麻煩。

而Laya的位圖文字則是一張整圖數字圖片,使用FontClip就能直接使用, 很方便。
所以現在弄個自定義的位圖文字類,也不用tm去導圖了。
二 決戰沙城的位圖文字代碼
先來看看別人的。據說這個框架里的位圖文字被用於很多大型H5 mmo項目。14年寫的工具類ε=(´ο`*)))唉。
主要原理就是1個字1個bitmap,然后並列排起來。“123“就是3個bitmap排起來。
/**
* 素材需要提前加載好
* 素材命名規則:類型_數值(有類型是因為一般會同時有幾種數字圖片,比如大小號或不同顏色)
* 點號素材命名:類型_dot
* 創建BitmapNumber使用createNumPic返回DisplayObjectContainer
* 創建好的BitmapNumber數值需要變化是調用changeNum
* 回收使用desstroyNumPic
*
* Created by Saco on 2014/8/1.
*/
class BitmapNumber extends SingtonClass {
private _imgPool:egret.Bitmap[];
private _containerPool:egret.DisplayObjectContainer[];
public constructor() {
super();
this._imgPool = [];
this._containerPool = [];
}
/*
* 根據需要的數字和類型返回一個DisplayObjectContainer
* num數字值,支持小數點
* type素材類型
* */
public createNumPic(num:number, type:string):egret.DisplayObjectContainer {
var container:egret.DisplayObjectContainer = this.getContainer();
var numStr:string = num.toString();
var index:number = 0;
var tempBm:egret.Bitmap;
for (index; index < numStr.length; index++) {
tempBm = this.getSingleNumPic(numStr.charAt(index), type);
container.addChild(tempBm);
}
this.repositionNumPic(container);
return container;
}
//回收帶數字的DisplayObjectContainer
public desstroyNumPic(picContainer:egret.DisplayObjectContainer):void {
this.clearContainer(picContainer);
if (picContainer.parent)
picContainer.parent.removeChild(picContainer);
this._containerPool.push(picContainer);
}
/*
* 改變帶數字的DisplayObjectContainer數字值
* 提供這個方法是為了提高效率,直接更換之前創建對象的texture,避免多余的刪除和創建
* */
public changeNum(picContainer:egret.DisplayObjectContainer, num:number, type:string):void {
var numStr:string = num.toString();
var tempBm:egret.Bitmap;
//如果當前數字個數多於目標個數則把多余的回收
if (picContainer.numChildren > numStr.length) {
while (picContainer.numChildren > numStr.length) {
this.recycleBM(<egret.Bitmap>picContainer.getChildAt(picContainer.numChildren - 1))
}
}
var index:number = 0;
var tempStr:string;
for (index; index < numStr.length; index++) {
//如果當前的Bitmap數量不夠則獲取新的Bitmap補齊
if (index >= picContainer.numChildren)
picContainer.addChild(this.getBitmap());
tempStr = numStr.charAt(index);
tempStr = tempStr == "." ? "dot" : tempStr;
(<egret.Bitmap>picContainer.getChildAt(index)).texture = this.getTexture(tempStr, type);
}
this.repositionNumPic(picContainer);
}
//每個數字寬度不一樣,所以重新排列
private repositionNumPic(container:egret.DisplayObjectContainer):void {
var index:number = 0;
var lastX:number = 0;
var temp:egret.DisplayObject;
for (index; index < container.numChildren; index++) {
temp = container.getChildAt(index);
temp.x = lastX;
lastX = temp.x + temp.width;
}
}
//清理容器
private clearContainer(picContainer:egret.DisplayObjectContainer):void {
while (picContainer.numChildren) {
this.recycleBM(<egret.Bitmap>picContainer.removeChildAt(0));
}
}
//回收Bitmap
private recycleBM(bm:egret.Bitmap):void {
if (bm && bm.parent) {
bm.parent.removeChild(bm);
bm.texture = null;
this._imgPool.push(bm);
}
}
private getContainer():egret.DisplayObjectContainer {
if (this._containerPool.length)
return this._containerPool.shift();
return new egret.DisplayObjectContainer();
}
//獲得單個數字Bitmap
private getSingleNumPic(num:string, type:string):egret.Bitmap {
if (num == ".")
num = "dot";
var bm:egret.Bitmap = this.getBitmap();
bm.texture = this.getTexture(num, type);
return bm;
}
private getTexture(num:string, type:string):egret.Texture {
return RES.getRes(type + num);
}
private getBitmap():egret.Bitmap {
if (this._imgPool.length)
return this._imgPool.shift();
return new egret.Bitmap();
}
}
順便看了下凡人修仙傳的位圖文字。可以看到文字是一整張合圖且無序排列,而且network里沒有fnt文件。推測應該也是使用支持單張數字的自定義位圖文字。

三 我自己寫了個
相對於決戰沙城的有些改動
1. 支持使用整圖和單張圖片。
2. 支持代碼創建和拖動到exml上擺放。
3. 文字圖片可以使用tm和其他圖片進行合並,減少drawcall和http請求,而不影響位圖文字的使用。
需要注意的是
1. 不要用cacheAsBitmap,對於變化數字非常卡。我試了下,卡爆了。
測試用整圖

測試用單張圖

具體代碼
/**
* 位圖文字
* @deprecated 可以使用單張整圖或者多張散圖,制作位圖文字。
* 注意"."的圖片命名要改為dot,例如"1"是font_1.png,那么"."的圖片命名是font_dot.png。
* @author ck 2019.11.10
*/
class BitmapFont extends eui.Component{
/**位圖緩存 */
private static bmCaches:Array<egret.Bitmap> = [];
/**紋理緩存 */
private static textureCaches = {};
/**顯示的文字 */
private _text:string;
/**圖片名 */
private pngName:string;
public constructor() {
super();
}
/**
* 文字在一張圖上
* @param pngName 圖片名 pngName = font_test (font_test.png)
* @param txt 文字名 "0123456789.+-"
*/
public static registerByOne(pngName:string, txt:string){
let textureCache = this.getTextureCache(pngName);
if(textureCache.length > 0){
console.log("位圖字體緩存已存在:",pngName);
return;
}
let src:egret.Texture = new egret.Texture();
src = RES.getRes(pngName + "_png");
let len = txt.length;
let fontWidth:number = src.textureWidth/len;
let fontHeight:number= src.textureHeight;
let texture:egret.Texture;
let rect:egret.Rectangle = new egret.Rectangle();
for(let i=0;i<len;i++){
texture = new egret.Texture();
texture.disposeBitmapData = false;
texture.$bitmapData = src.$bitmapData
texture.$initData(i*fontWidth,0, fontWidth, fontHeight, 0, 0, fontWidth, fontHeight, src.textureWidth, src.textureHeight);
textureCache[txt.charAt(i)] = texture;
}
}
/**
* 文字在不同的圖片上
* @param pngName 圖片名 pngName=font_test 多張圖片源文件名(font_test0.png font_test1.png .... font_testdot.png)
* @param txt 文字名 "0123456789.+-"
*/
public static registerByMulti(pngName:string, txt:string){
let textureCache = this.getTextureCache(pngName);
if(textureCache.length > 0){
console.log("位圖字體緩存已存在:",pngName);
return;
}
let len = txt.length;
let char:string;
for(let i=0;i<len;i++){
char = txt.charAt(i);
if(char == "."){
textureCache[char] = RES.getRes(pngName + "dot" + "_png");
}else{
textureCache[char] = RES.getRes(pngName + char + "_png");
}
}
}
/**
* 獲取紋理緩存
* @param pngName 圖片名
*/
public static getTextureCache(pngName:string){
let textureCache = BitmapFont.textureCaches[pngName];
if(textureCache == null){
textureCache = [];
BitmapFont.textureCaches[pngName] = textureCache;
}
return textureCache;
}
/**
* 設置文字
* @param txt 文字
*/
public set text(txt:string){
let bmCaches = BitmapFont.bmCaches;
let textureCache = BitmapFont.getTextureCache(this.pngName);
let curLen = this.numChildren;
let targetLen = txt.length;
this._text = txt;
//文字存在,且大於顯示文字,則移除多余文字
if(curLen > targetLen){
let bm:egret.Bitmap;
for(let i=curLen-1;i>=targetLen;i--){
bm = this.removeChildAt(i) as egret.Bitmap;
bm.texture = null;
bmCaches.push(bm);
}
}
//顯示文字
let bm:egret.Bitmap;
let tempX:number = 0;
let char:string;
for(let i=0;i<targetLen;i++){
//少於顯示文字,則增加文字
if(i >= curLen){
if(bmCaches.length > 0){
bm = bmCaches.pop();
}else{
bm = new egret.Bitmap();
}
this.addChild(bm);
}
bm = this.getChildAt(i) as egret.Bitmap;
bm.texture = textureCache[txt.charAt(i)];
bm.x = tempX;
tempX = bm.x + bm.width;
}
}
/**
* 獲取文字
*/
public get text(){
return this._text;
}
/**
* 設置文字類型
* @value 字體類型 文字是font_test.png一張圖片,則value = "font_test""。 若文字是font_test0.png font1_test1.png..多張圖片,則value="font_test"
*/
public set font(value:string){
this.pngName = value;
}
/**銷毀 */
public destroy(){
//回收bitmap
let len = this.numChildren;
let bm:egret.Bitmap;
let bmCaches = BitmapFont.bmCaches;
for(let i=len-1;i>=0;i--){
bm = this.getChildAt(i) as egret.Bitmap;
this.removeChild(bm);
bm.texture = null;
bmCaches.push(bm);
}
//從視圖移除
this.parent && this.parent.removeChild(this);
}
}
使用示例
//注冊
BitmapFont.registerByOne("font_test","02345");
//使用
let mc:BitmapFont = new BitmapFont();
mc.font = "font_test";
mc.text = "0222";
this.addChild(mc);
運行效果

四 測試下效率
新建n個位圖文字放到列表bmList,然后不停的切換數字顯示
private onEnterFrame(){
for(let i=0;i<this.bmList.length;i++){
//BitmapNumber.ins().changeNum(this.bmList[i],(Math.random()>0.5)?200000:200,"font" );
this.bmList[i].text = (Math.random()>0.5)?"200000":"200";
}
}
200個 1000個 2000個 4000個
自定義字體 60FPS 58-60FPS 30FPS 8FPS
BtimapLabl 60FPS 59-60FPS 59-60FPS 30FPS
決戰沙城 60FPS 59-60FPS 24FPS 8FPS
