如何在CocosCreator里畫(huà)個(gè)炫酷的雷達(dá)圖
前言
雷達(dá)圖(Radar Chart) 也稱為網(wǎng)絡(luò)圖、星圖或蜘蛛網(wǎng)圖。
是以從同一點(diǎn)開(kāi)始的軸上表示的三個(gè)或更多個(gè)定量變量的二維圖表的形式顯示多元數(shù)據(jù)的圖形方法。
適用于顯示三個(gè)或更多的維度的變量。

雷達(dá)圖常用于📚數(shù)據(jù)統(tǒng)計(jì)或?qū)Ρ?,?duì)于查看哪些變量具有相似的值、變量之間是否有異常值都很有用。
同時(shí)在不少游戲中都有雷達(dá)圖的身影,可以很直觀地展示并對(duì)比一些數(shù)據(jù)。
例如王者榮耀中的對(duì)戰(zhàn)資料中就用到了:

那么在本篇文章中,皮皮就來(lái)分享下在 Cocos Creator 中如何利用 Graphics 組件來(lái)繪制炫酷的雷達(dá)圖~
文中會(huì)對(duì)原始代碼進(jìn)行一定的削減以保證閱讀體驗(yàn)。
雷達(dá)圖組件:https://gitee.com/ifaswind/eazax-ccc/blob/master/components/RadarChart.ts
預(yù)覽
先來(lái)看看效果吧~
在線預(yù)覽:https://ifaswind.gitee.io/eazax-cases/?case=radarChart
兩條數(shù)據(jù)

緩動(dòng)數(shù)據(jù)

花里胡哨

藝術(shù)就是爆炸

逐漸偏離主題

正文
Graphics 組件
在我們正式開(kāi)始制作雷達(dá)圖之前,讓我們先來(lái)大概了解一下 Cocos Creator 引擎中的 Graphics 組件。
Graphics 組件繼承于 cc.RenderComponent,利用該組件我們可以實(shí)現(xiàn)畫(huà)板和表格之類的功能。
屬性(Properties)
下面是我們本次將會(huì)用到的屬性:
lineCap:設(shè)置或返回線條兩端的樣式(無(wú)、圓形線帽或方形線帽)lineJoin:設(shè)置或返回兩條線相交時(shí)的拐角樣式(斜角、圓角或尖角)lineWidth:設(shè)置或返回當(dāng)前畫(huà)筆的粗細(xì)(線條的寬度)strokeColor:設(shè)置或返回當(dāng)前畫(huà)筆的顏色fillColor:設(shè)置或返回填充用的顏色(油漆桶)
函數(shù)(Functions)
下面是我們本次將會(huì)用到的函數(shù):
moveTo(x, y):抬起畫(huà)筆并移動(dòng)到指定位置(不創(chuàng)建線條)lineTo(x, y):放下畫(huà)筆并創(chuàng)建一條直線至指定位置circle(cx, cy, r):在指定位置(圓心)畫(huà)一個(gè)圓close():閉合已創(chuàng)建的線條(相當(dāng)于lineTo(起點(diǎn)))stroke():繪制已創(chuàng)建(但未被繪制)的線條(將線條想象成默認(rèn)透明的,此行為則是賦予線條顏色)fill():填充當(dāng)前線條包圍的區(qū)域(如果線條沒(méi)有閉合則會(huì)嘗試”模擬閉合“起點(diǎn)和終點(diǎn))clear():擦掉當(dāng)前畫(huà)板上的所有東西
Graphics 組件文檔:http://docs.cocos.com/creator/manual/zh/components/graphics.html?h=graphics
畫(huà)網(wǎng)格
先來(lái)看看一個(gè)標(biāo)準(zhǔn)的雷達(dá)圖有啥特點(diǎn):

發(fā)現(xiàn)了嗎?雷達(dá)圖的基本特點(diǎn)如下:
- 有 3 條或以上的軸線
- 軸與軸之間的夾角相同
- 每條軸上除中心點(diǎn)外應(yīng)至少有 1 個(gè)刻度
- 每條軸上都有相同的刻度
- 刻度與刻度之間的距離也相同
- 軸之間的刻度相連形成網(wǎng)格線
計(jì)算軸線角度
先算出軸之間的夾角度數(shù) [ 360 ÷ 軸數(shù) ],再計(jì)算所有軸的角度:
this.angles = [];
// 軸間夾角
const iAngle = 360 / this.axes;
for (let i = 0; i < this.axes; i++) {
// 計(jì)算
const angle = iAngle * i;
this.angles.push(angle);
}
計(jì)算刻度坐標(biāo)
雷達(dá)圖至少擁有 3 條軸,且每條軸上都應(yīng)有 1 個(gè)或以上的刻度(不包含中心點(diǎn)):

所以我們需使用一個(gè)二維數(shù)組來(lái)保存所有刻度的坐標(biāo),從最外層(即軸線的末端)的刻度開(kāi)始記錄,方便我們繪制時(shí)讀?。?/p>
// 創(chuàng)建一個(gè)二維數(shù)組
let scalesSet: cc.Vec2[][] = [];
for (let i = 0; i < 軸上刻度個(gè)數(shù); i++) {
// 用來(lái)保存當(dāng)前層上的刻度坐標(biāo)
let scales = [];
// 計(jì)算刻度在軸上的位置
const length = 軸線長(zhǎng)度 - (軸線長(zhǎng)度 / 軸上刻度個(gè)數(shù) * i);
for (let j = 0; j < this.angles.length; j++) {
// 將角度轉(zhuǎn)為弧度
const radian = (Math.PI / 180) * this.angles[j];
// 根據(jù)三角公式計(jì)算刻度相對(duì)于中心點(diǎn)(0, 0)的坐標(biāo)
const pos = cc.v2(length * Math.cos(radian), length * Math.sin(radian));
// 推進(jìn)數(shù)組
scales.push(pos);
}
// 推進(jìn)二維數(shù)組
scalesSet.push(scales);
}
繪制軸線和外網(wǎng)格線
軸線
連接中心點(diǎn) (0, 0) 和最外層 scalesSet[0] 的刻度即為軸線:
// 遍歷全部最外層的刻度
for (let i = 0; i < scalesSet[0].length; i++) {
// 畫(huà)筆移動(dòng)至中心點(diǎn)
this.graphics.moveTo(0, 0);
// 創(chuàng)建線條
this.graphics.lineTo(scalesSet[0][i].x, scalesSet[0][i].y);
}
外網(wǎng)格線
連接所有軸上最外層 scalesSet[0] 的刻度即形成外網(wǎng)格線:
// 畫(huà)筆移動(dòng)至第一個(gè)點(diǎn)
this.graphics.moveTo(scalesSet[0][0].x, scalesSet[0][0].y);
for (let i = 1; i < scalesSet[0].length; i++) {
// 創(chuàng)建線條
this.graphics.lineTo(scalesSet[0][i].x, scalesSet[0][i].y);
}
// 閉合當(dāng)前線條(外網(wǎng)格線)
this.graphics.close();
填充并繪制
這里需要注意先填充顏色再繪制線條,要不然軸線和網(wǎng)格線就被擋住了:
// 填充線條包圍的空白區(qū)域 this.graphics.fill(); // 繪制已創(chuàng)建的線條(軸線和外網(wǎng)格線) this.graphics.stroke();
于是現(xiàn)在我們就有了這么個(gè)玩意兒:

繪制內(nèi)網(wǎng)格線
當(dāng)刻度大于 1 個(gè)時(shí)就需要繪制內(nèi)網(wǎng)格線,從刻度坐標(biāo)集的下標(biāo) 1 開(kāi)始繪制:
// 刻度大于 1 個(gè)時(shí)才繪制內(nèi)網(wǎng)格線
if (scalesSet.length > 1) {
// 從下邊 1 開(kāi)始(下標(biāo) 0 是外網(wǎng)格線)
for (let i = 1; i < scalesSet.length; i++) {
// 畫(huà)筆移動(dòng)至第一個(gè)點(diǎn)
this.graphics.moveTo(scalesSet[i][0].x, scalesSet[i][0].y);
for (let j = 1; j < scalesSet[i].length; j++) {
// 創(chuàng)建線條
this.graphics.lineTo(scalesSet[i][j].x, scalesSet[i][j].y);
}
// 閉合當(dāng)前線條(內(nèi)網(wǎng)格線)
this.graphics.close();
}
// 繪制已創(chuàng)建的線條(內(nèi)網(wǎng)格線)
this.graphics.stroke();
}
就這樣我們雷達(dá)圖的底子就畫(huà)好啦:

畫(huà)數(shù)據(jù)
編寫(xiě)畫(huà)線邏輯之前,先確定一下我們需要的數(shù)據(jù)結(jié)構(gòu):
- 數(shù)值數(shù)組(必須,小數(shù)形式的比例,至少包含 3 個(gè)值)
- 線的寬度(可選,不指定則使用默認(rèn)值)
- 線的顏色(可選,不指定則使用默認(rèn)值)
- 填充的顏色(可選,不指定則使用默認(rèn)值)
- 節(jié)點(diǎn)的顏色(可選,不指定則使用默認(rèn)值)
具體的數(shù)據(jù)結(jié)構(gòu)如下(導(dǎo)出類型方便外部使用):
/**
* 雷達(dá)圖數(shù)據(jù)
*/
export interface RadarChartData {
/** 數(shù)值 */
values: number[];
/** 線的寬度 */
lineWidth?: number;
/** 線的顏色 */
lineColor?: cc.Color;
/** 填充的顏色 */
fillColor?: cc.Color;
/** 節(jié)點(diǎn)的顏色 */
joinColor?: cc.Color;
}
繪制數(shù)據(jù)
繪制數(shù)據(jù)比較簡(jiǎn)單,我們只需要算出數(shù)據(jù)點(diǎn)在圖表中的位置,并將數(shù)據(jù)連起來(lái)就好了。
在 draw 函數(shù)中我們接收一份或以上的雷達(dá)圖數(shù)據(jù),并按照順序遍歷繪制出來(lái)(⚠️長(zhǎng)代碼警告):
/**
* 繪制數(shù)據(jù)
* @param data 數(shù)據(jù)
*/
public draw(data: RadarChartData | RadarChartData[]) {
// 處理數(shù)據(jù)
const datas = Array.isArray(data) ? data : [data];
// 開(kāi)始繪制數(shù)據(jù)
for (let i = 0; i < datas.length; i++) {
// 裝填染料
this.graphics.strokeColor = datas[i].lineColor || defaultOptions.lineColor;
this.graphics.fillColor = datas[i].fillColor || defaultOptions.fillColor;
this.graphics.lineWidth = datas[i].lineWidth || defaultOptions.lineWidth;
// 計(jì)算節(jié)點(diǎn)坐標(biāo)
let coords = [];
for (let j = 0; j < this.axes; j++) {
const value = datas[i].values[j] > 1 ? 1 : datas[i].values[j];
const length = value * this.axisLength;
const radian = (Math.PI / 180) * this.angles[j];
const pos = cc.v2(length * Math.cos(radian), length * Math.sin(radian))
coords.push(pos);
}
// 創(chuàng)建線條
this.graphics.moveTo(coords[0].x, coords[0].y);
for (let j = 1; j < coords.length; j++) {
this.graphics.lineTo(coords[j].x, coords[j].y);
}
this.graphics.close(); // 閉合線條
// 填充包圍區(qū)域
this.graphics.fill();
// 繪制線條
this.graphics.stroke();
// 繪制數(shù)據(jù)節(jié)點(diǎn)
for (let j = 0; j < coords.length; j++) {
// 大圓
this.graphics.strokeColor = datas[i].lineColor || defaultOptions.lineColor;
this.graphics.circle(coords[j].x, coords[j].y, 2);
this.graphics.stroke();
// 小圓
this.graphics.strokeColor = datas[i].joinColor || defaultOptions.joinColor;
this.graphics.circle(coords[j].x, coords[j].y, .65);
this.graphics.stroke();
}
}
}
到這里我們已經(jīng)成功制作了一個(gè)可用的雷達(dá)圖:

但是!我們的征途是星辰大海!必須加點(diǎn)料!
完全靜態(tài)的雷達(dá)圖實(shí)在是太無(wú)趣太普通,得想想辦法讓它動(dòng)起來(lái)!
我們的雷達(dá)圖數(shù)據(jù)的數(shù)值是數(shù)組形式,想到怎么樣才能讓這些數(shù)值動(dòng)起來(lái)了嗎?
得益于 Cocos Creator 為我們提供的 Tween 緩動(dòng)系統(tǒng),讓復(fù)雜的數(shù)據(jù)動(dòng)起來(lái)變得異常簡(jiǎn)單!
我們只需要這樣,這樣,然后那樣,是不是很簡(jiǎn)單?
cc.tween支持緩動(dòng)任意對(duì)象的任意屬性緩動(dòng)系統(tǒng):http://docs.cocos.com/creator/manual/zh/scripting/tween.html
另外我在《一個(gè)全能的挖孔 Shader》中也是使用了緩動(dòng)系統(tǒng)來(lái)讓挖孔動(dòng)起來(lái)~
在線預(yù)覽:https://ifaswind.gitee.io/eazax-cases/?case=newGuide
我的思路是:
- 將當(dāng)前的數(shù)據(jù)保存到當(dāng)前實(shí)例的
this.curDatas中 - 接收到新的數(shù)據(jù)時(shí),使用
cc.tween對(duì)this.curData的屬性進(jìn)行緩動(dòng) - 在
update中調(diào)用draw函數(shù),每幀都重新繪制this.curDatas中的數(shù)據(jù)
每幀更新
// 當(dāng)前雷達(dá)圖數(shù)據(jù)
private curDatas: RadarChartData[] = [];
protected update() {
if (!this.keepUpdating) return;
// 繪制當(dāng)前數(shù)據(jù)
this.draw(this.curDatas);
}
緩動(dòng)數(shù)據(jù)
/**
* 緩動(dòng)繪制
* @param data 目標(biāo)數(shù)據(jù)
* @param duration 動(dòng)畫(huà)時(shí)長(zhǎng)
*/
public to(data: RadarChartData | RadarChartData[], duration: number) {
// 處理重復(fù)調(diào)用
this.unscheduleAllCallbacks();
// 包裝單條數(shù)據(jù)
const datas = Array.isArray(data) ? data : [data];
// 打開(kāi)每幀更新
this.keepUpdating = true;
// 動(dòng)起來(lái)!
for (let i = 0; i < datas.length; i++) {
// 數(shù)值動(dòng)起來(lái)!
// 遍歷數(shù)據(jù)中的全部數(shù)值,逐個(gè)讓他們動(dòng)起來(lái)!
for (let j = 0; j < this.curDatas[i].values.length; j++) {
// 限制最大值為 1(即 100%)
const value = datas[i].values[j] > 1 ? 1 : datas[i].values[j];
cc.tween(this.curDatas[i].values)
.to(duration, { [j]: value })
.start();
}
// 樣式動(dòng)起來(lái)!
// 沒(méi)有指定則使用原來(lái)的樣式!
cc.tween(this.curDatas[i])
.to(duration, {
lineWidth: datas[i].lineWidth || this.curDatas[i].lineWidth,
lineColor: datas[i].lineColor || this.curDatas[i].lineColor,
fillColor: datas[i].fillColor || this.curDatas[i].fillColor,
joinColor: datas[i].joinColor || this.curDatas[i].joinColor
})
.start();
}
this.scheduleOnce(() => {
// 關(guān)閉每幀更新
this.keepUpdating = false;
}, duration);
}
數(shù)值和樣式都動(dòng)起來(lái)了:

雷達(dá)圖組件:https://gitee.com/ifaswind/eazax-ccc/blob/master/components/RadarChart.ts
以上就是如何在CocosCreator里畫(huà)個(gè)炫酷的雷達(dá)圖的詳細(xì)內(nèi)容,更多關(guān)于CocosCreator畫(huà)個(gè)雷達(dá)圖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Unity使用物理引擎實(shí)現(xiàn)多旋翼無(wú)人機(jī)的模擬飛行
- Android下2d物理引擎Box2d用法簡(jiǎn)單實(shí)例
- 解讀CocosCreator源碼之引擎啟動(dòng)與主循環(huán)
- CocosCreator通用框架設(shè)計(jì)之資源管理
- 如何在CocosCreator中做一個(gè)List
- 剖析CocosCreator新資源管理系統(tǒng)
- CocosCreator骨骼動(dòng)畫(huà)之龍骨DragonBones
- 詳解CocosCreator制作射擊游戲
- 詳解CocosCreator MVC架構(gòu)
- 怎樣在CocosCreator中使用物理引擎關(guān)節(jié)
相關(guān)文章
JS運(yùn)動(dòng)特效之完美運(yùn)動(dòng)框架實(shí)例分析
這篇文章主要介紹了JS運(yùn)動(dòng)特效之完美運(yùn)動(dòng)框架,結(jié)合實(shí)例形式分析了javascript針對(duì)運(yùn)動(dòng)中的元素屬性檢測(cè)與判斷相關(guān)操作技巧,需要的朋友可以參考下2018-01-01
Javascript & DHTML DOM基礎(chǔ)和基本API
DOM是文檔對(duì)象模型(Document Object Model,是基于瀏覽器編程(在本教程中,可以說(shuō)就是DHTML編程)的一套API接口,W3C出臺(tái)的推薦標(biāo)準(zhǔn),每個(gè)瀏覽器都有一些細(xì)微的差別,其中以Mozilla的瀏覽器最與標(biāo)準(zhǔn)接近。2008-07-07
js優(yōu)化針對(duì)IE6.0起作用(詳細(xì)整理)
js優(yōu)化針對(duì)IE6.0起作用,總結(jié)一下幾點(diǎn):字符串拼接、for 循環(huán)、減少頁(yè)面的重繪、減少作用域鏈上的查找次數(shù)、避免雙重解釋等等,需要了解的朋友可以參考下,或許會(huì)有所幫助2012-12-12
原生微信小程序/uniapp使用空格占位符無(wú)效的解決辦法
最近需要在字體中間加空白占位符,在嘗試使用 之后,還是不能使用,下面這篇文章主要給大家介紹了關(guān)于原生微信小程序/uniapp使用空格占位符無(wú)效的解決辦法,需要的朋友可以參考下2023-02-02

