如何在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à)板和表格之類(lèi)的功能。
屬性(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)出類(lèi)型方便外部使用):
/** * 雷達(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-01Javascript & 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-07js優(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