游戲賬號(hào)大圖生成
1 背景
2 從前端生成大圖,到后端生成大圖
2.1 前端生成大圖的考量與實(shí)現(xiàn)方案
2.2 前端生成大圖遇到的問(wèn)題及原因分析
2.3 改進(jìn)方案-后端生成大圖
3. 后端生成大圖的具體實(shí)現(xiàn)
3.1 Java圖像處理api
3.2 基礎(chǔ)信息部分
3.3 模塊化拼接皮膚分類(lèi)部分
4 總結(jié)
4.1 性能提升
4.2 用戶(hù)體驗(yàn)
4.3 擴(kuò)展性與維護(hù)性
1.背景
轉(zhuǎn)轉(zhuǎn)游戲賬號(hào)業(yè)務(wù)中,以王者榮耀為例,用戶(hù)關(guān)注的核心指標(biāo)是:英雄總數(shù)、皮膚總數(shù)、特色皮膚等重點(diǎn)信息。但是在舊版商品列表展示的圖片,是用戶(hù)上傳的圖片,以及一些簡(jiǎn)單業(yè)務(wù)規(guī)則生成的圖片,沒(méi)有突出賬號(hào)相關(guān)重點(diǎn)特色。
圖片
雖然在商詳(商品詳情頁(yè))內(nèi)部已經(jīng)呈現(xiàn)出了完整的驗(yàn)號(hào)報(bào)告,但是用戶(hù)需要從商列(商品列表頁(yè))點(diǎn)擊進(jìn)入到商詳,存在較長(zhǎng)的轉(zhuǎn)換路徑,降低了關(guān)鍵信息的觸達(dá)效率。
圖片
于是我們將游戲賬號(hào)相關(guān)重點(diǎn)信息生成大圖,展示在商列及商詳。
商列(商品列表頁(yè))大圖:
圖片
商詳(商品詳情頁(yè))大圖:
圖片
2.從前端生成大圖,到后端生成大圖
2.1 前端生成大圖的考量與實(shí)現(xiàn)方案
需求初期,大圖由前端生成,主要出于以下考慮:
- 前端可利用瀏覽器原生渲染能力處理復(fù)雜布局和樣式,開(kāi)發(fā)驗(yàn)證速度較快。
- 前端能更靈活地支持圖像處理,開(kāi)發(fā)成本較低。
前后端進(jìn)行交互,由后端提供生成圖片所需的全量物料圖片信息。前端拿到物料之后,通過(guò)頁(yè)面api,與Puppeteer截圖相結(jié)合的方式生成大圖。步驟如下:
- 前端創(chuàng)建一個(gè)頁(yè)面,通過(guò)訪問(wèn)后端接口拿到對(duì)應(yīng)的物料數(shù)據(jù)(小圖url、文字等)。對(duì)這些圖片資源url數(shù)據(jù)進(jìn)行實(shí)時(shí)訪問(wèn),在頁(yè)面上進(jìn)行圖片與文字的繪制,進(jìn)而生成大圖的頁(yè)面。
- 將此頁(yè)面的地址,傳到另一個(gè)Puppeteer服務(wù)。Puppeteer服務(wù)會(huì)啟動(dòng)一個(gè)瀏覽器實(shí)例,再次訪問(wèn)傳入的頁(yè)面,然后進(jìn)行截圖。
2.2 前端生成大圖遇到的問(wèn)題及原因分析
在上線之后,發(fā)現(xiàn)前端生成大圖會(huì)有一定概率的超時(shí)異常導(dǎo)致生成圖片失敗,平均2~3s,超時(shí)情況大于5s。在生成只有12個(gè)拼接圖片的情況下尚且超時(shí),隨著我們商詳大圖需求的引入,拼接圖片數(shù)量有時(shí)會(huì)超過(guò)600。此時(shí)超時(shí)情況會(huì)更加嚴(yán)重。
通過(guò)對(duì)整體鏈路的分析,發(fā)現(xiàn)Puppeteer服務(wù)截圖是一個(gè)耗時(shí)較多的操作,其大概步驟如下:
- 申請(qǐng)服務(wù)內(nèi)存、磁盤(pán)等資源;
- 創(chuàng)建瀏覽器進(jìn)程并啟動(dòng);
- 通過(guò)網(wǎng)絡(luò)訪問(wèn)傳入頁(yè)面URL;
- 渲染頁(yè)面并且進(jìn)行截圖。
2.3 改進(jìn)方案-后端生成大圖
為了解決前端方案的性能瓶頸,我們?cè)u(píng)估了遷移到后端生成圖片的可行性。對(duì)于上面遇到的問(wèn)題,后端可以有針對(duì)性地進(jìn)行解決:使用Java中awt包下的畫(huà)圖api拼接生成大圖,避免了Puppeteer啟動(dòng)瀏覽器、渲染頁(yè)面帶來(lái)的開(kāi)銷(xiāo)。
能否遷移到后端,有兩個(gè)衡量標(biāo)準(zhǔn),第一個(gè)是性能,即耗時(shí)。第二個(gè)是后端生成的圖片UI樣式,即能否達(dá)到UI驗(yàn)收標(biāo)準(zhǔn)。
于是我們先在本地測(cè)試,發(fā)現(xiàn)相同圖片耗時(shí)僅僅需要20ms左右(相較前端平均2~3秒的時(shí)間有較大提升)。即使涉及500多張圖片的拼接,平均耗時(shí)也只在2s左右。另外在生成的圖片樣式效果上也達(dá)到了UI驗(yàn)收的要求。
前端生成大圖:
圖片
后端生成大圖:
圖片
3. 后端生成大圖的具體實(shí)現(xiàn)
以上文提到的、規(guī)則相對(duì)復(fù)雜的商品詳情頁(yè)大圖為例進(jìn)行說(shuō)明。通過(guò)分析UI原型,我們發(fā)現(xiàn)其結(jié)構(gòu)具有清晰的模塊化特征:從上往下看依次為基礎(chǔ)信息模塊、分類(lèi)皮膚信息模塊。其中皮膚信息模塊又可分為分類(lèi)標(biāo)題模塊、單個(gè)皮膚單元模塊。
圖片
這里需要用到的操作,包括繪制圖片、文字、伸縮圖片、平移圖片等。由于后端使用的編程語(yǔ)言為Java,所以先簡(jiǎn)要介紹一下Java圖片處理相關(guān)的api。
3.1 Java圖像處理api
在Java圖像處理中,java.awt.image.BufferedImage.BufferedImage與java.awt.Graphics2D是兩個(gè)核心類(lèi),它們密切協(xié)作以實(shí)現(xiàn)圖像的創(chuàng)建、編輯和渲染。
- BufferedImage是圖像數(shù)據(jù)的畫(huà)布容器,負(fù)責(zé)存儲(chǔ)像素信息。主要用于讀寫(xiě)圖像文件。
- Graphics2D是操作圖像的畫(huà)筆,負(fù)責(zé)繪制和修改圖像內(nèi)容。在BufferedImage上繪制內(nèi)容。支持繪制文字、圖像平移縮放等操作。
3.1.1 創(chuàng)建BufferedImage
/**
* 讀取本地文件
*/
BufferedImage imageFromFile = ImageIO.read(new File("本地圖片路徑"));
/**
* 從網(wǎng)絡(luò)中讀取圖片
*/
BufferedImage imageFromUrl= ImageIO.read(new URL("網(wǎng)絡(luò)圖片路徑"));
/**
* 通過(guò)構(gòu)造方法創(chuàng)建。構(gòu)造參數(shù)指定寬和高。
*/
BufferedImage combinedImage = new BufferedImage(100, 200, BufferedImage.TYPE_INT_RGB);3.1.2 繪制圖片與文字
public static void main(String[] args) throws IOException {
BufferedImage backGroundImage = ImageIO.read(new File("輸入路徑"));
BufferedImage combinedImage = new BufferedImage(backGroundImage.getWidth(), backGroundImage.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = combinedImage.createGraphics();
try {
// 畫(huà)圖
graphics.drawImage(backGroundImage, 0, 0, null);
// 寫(xiě)文字
graphics.setFont(new Font("微軟雅黑", Font.BOLD, 20));
graphics.setColor(Color.WHITE);
graphics.drawString("文字內(nèi)容", 20, 20);
} finally {
// 釋放資源
graphics.dispose();
}
// 保存結(jié)果
File output = new File("輸出路徑");
ImageIO.write(combinedImage, "jpg", output);
}3.1.3 圖片伸縮
public static void main(String[] args) throws IOException {
// 伸縮前的畫(huà)面
BufferedImage originImage = ImageIO.read(new File("文件路徑"));
// 伸縮后的畫(huà)面
BufferedImage scaleImage = getScaleImage(originImage, 0.7, 0.7);
File output = new File("輸出路徑");
ImageIO.write(scaleImage, "jpg", output);
}
/**
* 對(duì)原始圖片按比例進(jìn)行伸縮
*/
public static BufferedImage getScaleImage(BufferedImage originImage, double scaleFactorWidth, double scaleFactorHeight) {
if (Objects.isNull(originImage) || scaleFactorWidth <= 0 || scaleFactorHeight <= 0) {
return originImage;
}
// 等比例壓縮比例
int scaledWidth = (int) (originImage.getWidth() * scaleFactorWidth);
int scaledHeight = (int) (originImage.getHeight() * scaleFactorHeight);
// 創(chuàng)建新的 BufferedImage
BufferedImage scaledImage = new BufferedImage(scaledWidth, scaledHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = scaledImage.createGraphics();
try {
// 繪制縮放后的圖像
g2d.drawImage(originImage, 0, 0, scaledWidth, scaledHeight, null);
} finally {
g2d.dispose();
}
return scaledImage;3.2 基礎(chǔ)信息部分
原始UI圖如下,我們需要將具體文字信息寫(xiě)入到對(duì)應(yīng)位置內(nèi)。
圖片
實(shí)現(xiàn)思路:
- 首先將原始UI圖繪制到畫(huà)布;
- 設(shè)置文字字體、大小,以及橫縱坐標(biāo)參數(shù),繪制文字。
效果圖如下:
圖片
3.3 模塊化拼接皮膚分類(lèi)部分
皮膚分類(lèi)模塊分開(kāi)來(lái)看,可以按照皮膚類(lèi)型分成若干個(gè)大類(lèi),如史詩(shī)皮膚、限定皮膚等。同時(shí)在每個(gè)皮膚分類(lèi)模塊前有對(duì)應(yīng)標(biāo)題。
3.3.1 繪制標(biāo)題
通過(guò)api繪制標(biāo)題圖片到指定位置即可,這樣可省去字體設(shè)置與字體居中的步驟。
圖片
3.3.2 生成皮膚圖片單元
每一個(gè)皮膚分類(lèi)中,各個(gè)皮膚單元都由四部分元素組成,分別是角標(biāo)圖片、底圖圖片、文字、浮層圖片。
圖片
我們拿到這四部分基礎(chǔ)原始數(shù)據(jù)之后,按如下步驟進(jìn)行繪制:
- 繪制皮膚底圖到畫(huà)布;
- 繪制浮層覆蓋到皮膚底圖之上;
- 繪制角標(biāo)圖片到指定位置;
- 繪制文字到指定位置。
由于各類(lèi)小圖片信息是短時(shí)間內(nèi)不會(huì)變更的,所以這里會(huì)對(duì)各類(lèi)小圖片進(jìn)行本地緩存,避免頻繁網(wǎng)絡(luò)請(qǐng)求導(dǎo)致的資源浪費(fèi)。
3.3.3 生成皮膚分類(lèi)模塊
將所有生成的皮膚圖片單元按指定橫縱坐標(biāo)繪制。
圖片
3.3.4 拉伸背景與邊框
原始UI切圖如下:
圖片
由于每個(gè)賬號(hào)對(duì)應(yīng)的皮膚數(shù)量不同,需要讓背景圖與邊框適配對(duì)應(yīng)數(shù)量的皮膚圖片總高度。
圖片
3.3.5 拼接生成各分類(lèi)組合大圖
通過(guò)對(duì)每個(gè)皮膚分類(lèi),重復(fù)以上步驟,即可生成各分類(lèi)組合大圖。
圖片
4.總結(jié)
4.1 性能提升
生成耗時(shí)從平均2-3秒(前端+Puppeteer)降至毫秒級(jí)(簡(jiǎn)單圖片)至秒級(jí)(超復(fù)雜圖片如500+皮膚),解決了超時(shí)問(wèn)題。
4.2 用戶(hù)體驗(yàn)
確保了用戶(hù)在瀏覽商品時(shí),能快速地獲取到游戲賬號(hào)的核心價(jià)值信息。
4.3 擴(kuò)展性與維護(hù)性
通過(guò)模塊化思想,將圖片拼接的核心邏輯(圖片加載、繪制、文字渲染、布局、背景處理)抽象為可復(fù)用的基礎(chǔ)服務(wù)模塊。再結(jié)合動(dòng)態(tài)配置來(lái)定義不同游戲的大圖布局、元素樣式、數(shù)據(jù)映射規(guī)則等,實(shí)現(xiàn)了業(yè)務(wù)邏輯與渲染邏輯的解耦。使新游戲品類(lèi)的接入效率大幅提升。
現(xiàn)已應(yīng)用在王者榮耀、原神、火影忍者、槍?xiě)?zhàn)王者等多款游戲。
原神:
圖片
火影忍者:
槍?xiě)?zhàn)王者:
關(guān)于作者
張廉潔 轉(zhuǎn)轉(zhuǎn)Java開(kāi)發(fā)工程師























