網頁小實驗——用canvas生成精靈動畫圖片
實驗目標:藉助canvas把一張國際象棋棋子圖片轉換為一組適用於WebGL渲染的精靈動畫圖片,不借助其他圖片處理工具,不引用其他庫只使用原生js實現。
初始圖片如下:
一、圖片分割
將初始圖片分割為六張大小相同的棋子圖片
1、html舞臺:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>處理棋子圖片</title> 6 </head> 7 <body> 8 <canvas id="can_source" style="z-index: 1;top:2px;left:2px;position: absolute"></canvas><!--顯示原圖的畫布--> 9 <canvas id="can_mask" style="z-index: 10;top:2px;left:2px;position: absolute"></canvas><!--顯示操作範圍提示的畫布--> 10 <canvas id="can_maskbak" style="z-index: 1;top:2px;left:2px;position: absolute"></canvas><!--用來劃分區域的背景畫布--> 11 </body> 12 <script><!--主體程式碼--> 13 </script> 14 </html>
這裡準備了三張canvas畫布,其中can_source是預覽原圖的畫布,稱為“源畫布”;can_mask是懸浮在can_source上層的透明背景畫布,用來繪製切割範圍提示,稱為“提示畫布”;can_maskbak用來圈定切割範圍(其實可以不顯示它),稱為“範圍畫布”。
2、分割流程:
1 var can_source=document.getElementById("can_source"); 2 var can_mask=document.getElementById("can_mask"); 3 var can_maskbak=document.getElementById("can_maskbak"); 4 var top_res; 5 var width=0,height=0; 6 window.onload=function(){ 7 var img=new Image(); 8 img.src="../../ASSETS/IMAGE/ICON/chesses.jpg"; 9 img.onload=function(){ 10 width=img.width;//根據圖片尺寸設定畫布尺寸 11 height=img.height; 12 can_source.style.width=width+"px";//css尺寸 13 can_source.style.height=height+"px"; 14 can_source.width=width;//canvas畫素尺寸 15 can_source.height=height; 16 var con_source=can_source.getContext("2d"); 17 con_source.drawImage(img,0,0);//顯示原圖 18 19 top_res=height+4+"px"; 20 can_maskbak.style.left=width+4+"px";//把這個圈定範圍的畫布放在右邊,做對比 21 can_maskbak.style.width=width+"px"; 22 can_maskbak.style.height=height+"px"; 23 can_maskbak.width=width; 24 can_maskbak.height=height; 25 var con_maskbak=can_maskbak.getContext("2d"); 26 con_maskbak.fillStyle="rgba(0,0,0,1)";//填充完全不透明的黑色 27 con_maskbak.fillRect(0,0,width,height); 28 29 can_mask.style.width=width+"px"; 30 can_mask.style.height=height+"px"; 31 can_mask.width=width; 32 can_mask.height=height; 33 var con_mask=can_mask.getContext("2d"); 34 con_mask.fillStyle="rgba(0,0,0,0)"; 35 con_mask.fillRect(0,0,width,height); 36 //下面是具體的操作程式碼 37 //cutRect(40,10,120,240,256,256);//矩形切割 38 //cutRect(192,10,120,240,256,256); 39 //cutRect(340,10,120,240,256,256); 40 cutRect(33,241,120,240,256,256); 41 cutRect(200,241,120,240,256,256); 42 cutRect(353,241,120,240,256,256); 43 } 44 }
3、具體切割演算法:
1 //從一個畫布上下文中剪下一塊dataUrl 2 function cutRect(x,y,wid,hig,wid2,hig2) 3 { 4 //將矩形轉換為路徑,然後用更一般化的路徑方法處理區域 5 var path=[{x:x,y:y},{x:x+wid,y:y},{x:x+wid,y:y+hig},{x:x,y:y+hig}]; 6 var framearea=[x,y,wid,hig];//framearea是操作範圍的邊界,矩形切割則直接是矩形本身,多邊形切割則應是多邊形的外切矩形範圍 7 cutPath(path,framearea,wid2,hig2); 8 9 } 10 function cutPath(path,framearea,wid2,hig2) 11 { 12 var len=path.length; 13 var con_mask=can_mask.getContext("2d"); 14 con_mask.strokeStyle="rgba(160,197,232,1)";//線框 15 con_mask.beginPath(); 16 for(var i=0;i<len;i++) 17 { 18 var point=path[i]; 19 if(i==0) 20 { 21 con_mask.moveTo(point.x,point.y); 22 } 23 else { 24 con_mask.lineTo(point.x,point.y); 25 } 26 27 } 28 con_mask.closePath();//在提示畫布中繪製提示框 29 con_mask.stroke(); 30 //con_mask.Path; 31 32 33 var con_maskbak=can_maskbak.getContext("2d"); 34 con_maskbak.beginPath(); 35 con_maskbak.fillStyle="rgba(0,255,0,1)"; 36 con_maskbak.lineWidth=0; 37 for(var i=0;i<len;i++) 38 { 39 var point=path[i]; 40 con_maskbak.lineTo(point.x,point.y); 41 } 42 con_maskbak.closePath(); 43 con_maskbak.fill();//在範圍畫布中畫出切割的範圍(純綠色) 44 45 var con_source=can_source.getContext("2d"); 46 var data_source=con_source.getImageData(framearea[0],framearea[1],framearea[2],framearea[3]);//獲取源畫布在操作範圍內的畫素 47 var data_maskbak=con_maskbak.getImageData(framearea[0],framearea[1],framearea[2],framearea[3]);//獲取範圍畫布在操作範圍內的畫素 48 49 var can_temp=document.createElement("canvas");//建立一個暫存canvas作為工具,並不實際顯示它。 50 can_temp.width=wid2||framearea[2];//設定暫存畫布的尺寸,這裡要把長方形的切圖儲存為正方形! 51 can_temp.height=hig2||framearea[3]; 52 var con_temp=can_temp.getContext("2d"); 53 con_temp.fillStyle="rgba(255,255,255,1)"; 54 con_temp.fillRect(0,0,can_temp.width,can_temp.height); 55 var data_res=con_temp.createImageData(framearea[2],framearea[3]);//建立暫存畫布大小的畫素資料 56 57 58 var len=data_maskbak.data.length; 59 for(var i=0;i<len;i+=4)//對於範圍畫布的每一個畫素 60 { 61 if(data_maskbak.data[i+1]=255)//如果這個畫素是綠色 62 { 63 data_res.data[i]=(data_source.data[i]);//則填充源畫布的對應畫素 64 data_res.data[i+1]=(data_source.data[i+1]); 65 data_res.data[i+2]=(data_source.data[i+2]); 66 data_res.data[i+3]=(data_source.data[i+3]); 67 } 68 else 69 { 70 data_res.data[i]=(255);//否則填充完全不透明的白色,注意不透明度通道在rgba表示中是0到1,在data表示中是0到255! 71 data_res.data[i+1]=(255); 72 data_res.data[i+2]=(255); 73 data_res.data[i+3]=(255); 74 } 75 } 76 con_temp.putImageData(data_res,(can_temp.width-framearea[2])/2,(can_temp.height-framearea[3])/2)//把填充完畢的畫素資料放置在暫存畫布的中間 77 console.log(can_temp.toDataURL());//以dataUrl方式輸出暫存畫布的資料 78 79 }
4、切割效果如下:
在控制檯裡可以找到以文字方式輸出的圖片資料:
對於小於2MB的圖片資料,直接複製dataUrl貼上到瀏覽器位址列回車,即可顯示完整圖片,之後右鍵儲存;對於大於2MB的圖片資料則需把can_temp顯示出來,之後右鍵儲存。精靈動畫的單幀圖片一般較小,所以不考慮需要顯示can_temp的情況。
最終獲取的一張“兵”圖片:
5、改進
其實canvas的path物件本身就有clip方法,可以用這個內建方法簡化以上過程。
clip方法的文件:https://www.w3school.com.cn/tags/canvas_clip.asp
二、生成精靈動畫
1、html舞臺及準備程式碼:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>建立棋子的動畫幀,新增一個圖示樣式</title> 6 </head> 7 <body> 8 <canvas id="can_back" style="z-index: 1;top:2px;left:2px;position: absolute"></canvas><!--徽章的背景--> 9 <canvas id="can_back2" style="z-index: 1;top:2px;left:2px;position: absolute"></canvas> 10 <canvas id="can_res" style="z-index: 1;top:2px;left:2px;position: absolute"></canvas><!--顯示結果--> 11 </body> 12 <script> 13 var can_back=document.getElementById("can_back"); 14 var can_back2=document.getElementById("can_back2"); 15 var can_res=document.getElementById("can_res"); 16 var width=240,height=360; 17 window.onload=function(){ 18 console.log("程式開始") 19 can_back.style.width=width+"px"; 20 can_back.width=width; 21 can_back.style.height=height+"px"; 22 can_back.height=height; 23 can_back2.style.width=width+"px"; 24 can_back2.width=width; 25 can_back2.style.height=height+"px"; 26 can_back2.height=height; 27 can_back2.style.left=width+4+"px"; 28 can_res.style.top=height+4+"px"; 29 var img=new Image(); 30 img.src="../../ASSETS/IMAGE/ICON/bing.png";//256*256的圖片 31 img.onload=function(){//動畫幀生成程式碼 32 } 33 </script> 34 </html>
2、在can_back中為棋子新增“徽章”背景
新增後效果如下:
為棋子添加了一個形狀和顏色漸變的徽章背景,徽章外為透明色,可以根據棋子所屬的勢力為徽章設定不同的主色調。演算法首先判斷can_back的畫素點是否在棋子“兵”內,如果在棋子內則原樣呈現,否則根據畫素的位置計算畫素的顏色,一種實現方法如下:
1 var con_back=can_back.getContext("2d"); 2 con_back.fillStyle="rgba(0,255,0,0)"; 3 con_back.fillRect(0,0,width,height); 4 con_back.drawImage(img,(width-256)/2,(height-256)/2) 5 6 var data_back=con_back.getImageData(0,0,width,height); 7 //var len=data_back.length; 8 var r1=22/19; 9 var r2=22/51; 10 var p_light=255;//背景強度 11 var i_min=0,i_max=0; 12 //一行一行地處理畫素 13 var data=data_back.data; 14 for(var i=0;i<360;i++) 15 { 16 var num_w=(Math.pow(110*110-(i-100)*(i-100)*r2*r2,0.5)); 17 for(var j=0;j<240;j++)//對於這一行裡的每一個畫素 18 { 19 var index=(i*240+j)*4; 20 if(i<5||i>355) 21 { 22 data[index]=0; 23 data[index+1]=255; 24 data[index+2]=0; 25 data[index+3]=0; 26 } 27 else 28 { 29 30 if(i<100) 31 { 32 if(Math.abs(j-119.5)<((i-5)*r1)) 33 { 34 if(data[index]+data[index+1]+data[index+2]>600||data[index+3]==0)//不是黑色或者完全透明 35 { 36 var b=127+128*(95-(i-5))/95;//保持紅色為主色調 37 var b2=(p_light-b)/2; 38 data[index]=b; 39 data[index+1]=b2; 40 data[index+2]=b2; 41 data[index+3]=255; 42 } 43 else 44 { 45 data[index]=0; 46 data[index+1]=0; 47 data[index+2]=0; 48 data[index+3]=255; 49 if(i_min==0) 50 { 51 i_min=i; 52 i_max=i; 53 } 54 else 55 { 56 if(i>i_max) 57 { 58 i_max=i; 59 } 60 } 61 } 62 } 63 else 64 { 65 data[index]=0; 66 data[index+1]=255; 67 data[index+2]=0; 68 data[index+3]=0; 69 } 70 } 71 else 72 { 73 //if(Math.abs(j-119.5)<Math.pow((355-i),0.5)*r2) 74 if(Math.abs(j-119.5)<num_w) 75 { 76 if(data[index]+data[index+1]+data[index+2]>600||data[index+3]==0)//不是黑色 77 { 78 var b=127+128*(255-(355-i))/255; 79 var b2=(p_light-b)/2; 80 data[index]=b; 81 data[index+1]=b2; 82 data[index+2]=b2; 83 data[index+3]=255; 84 } 85 else 86 { 87 data[index]=0; 88 data[index+1]=0; 89 data[index+2]=0; 90 data[index+3]=255; 91 if(i_min==0) 92 { 93 i_min=i; 94 i_max=i; 95 } 96 else 97 { 98 if(i>i_max) 99 { 100 i_max=i; 101 } 102 } 103 } 104 } 105 else 106 { 107 data[index]=0; 108 data[index+1]=255; 109 data[index+2]=0; 110 data[index+3]=0; 111 } 112 } 113 } 114 } 115 } 116 con_back.putImageData(data_back,0,0);View Code
3、在can_back2為徽章中的棋子描邊
為後面的環節做準備,給棋子的輪廓描一層rgb(1,1,1)顏色、2px寬度的邊
1 var size_border=2; 2 var rgb_border={r:1,g:1,b:1}; 3 if(size_border>0)//為前景和背景的邊界描邊的演算法? 4 {//-》為特定的兩種顏色邊界描邊的演算法!!!! 5 console.log("開始描繪邊界"); 6 drawBorder(data,240,360,isColorOut,isColorIn,Math.floor(size_border/2),size_border,rgb_border); 7 }//引數:畫素資料,寬度,高度,判斷畫素在描邊內測的條件,判斷畫素在描邊外側的條件,描邊的偏移,邊寬,描邊的顏色 8 var con_back2=can_back2.getContext("2d"); 9 con_back2.putImageData(data_back,0,0);
描邊函式:
1 function isColorOut(rgba) 2 { 3 if(rgba.r>127) 4 { 5 return true; 6 } 7 return false; 8 } 9 function isColorIn(rgba) 10 { 11 if(rgba.r==0&&rgba.g==0&&rgba.b==0) 12 { 13 return true; 14 } 15 return false; 16 } 17 //引數:畫素資料,圖片的寬度,圖片的高度,”外部“的顏色(可以有多種),“內部的顏色”(可以有多種,但不應與arr_rgba1重複!!) 18 // ,決定把邊畫在內部還是外部的偏移(預設為0,畫在中間?為正表示向內偏),邊的寬度,邊的顏色(認為完全不透明) 19 //使用xy的垂直遍歷方法,另一種思路是讓計算核沿著分界線移動《-繪製的更為平滑 20 //function drawBorder(data,width,height,arr_rgbaout,arr_rgbain,offset_inout,size_border,rgb_border) 21 //內外的顏色可能是漸變的!!所以在這裡用返回布林值的函式做引數!!!!而非固定顏色範圍 22 function drawBorder(data,width,height,func_out,func_in,offset_inout,size_border,rgb_border) 23 { 24 //首先對於每一行畫素 25 for(var i=0;i<height;i++) 26 { 27 var lastRGBA={}; 28 for(var j=0;j<width;j++) 29 { 30 var index=(i*240+j)*4; 31 var RGBA={r:data[index],g:data[index+1],b:data[index+2],a:data[index+3]}; 32 //if(!lastRGBA.r&&lastRGBA.r!=0)//如果是第一個畫素 33 if(j==0) 34 { 35 lastRGBA=RGBA;//上一顏色 36 continue; 37 } 38 else 39 { 40 //if(isRGBAinArr(arr_rgbaout,lastRGBA)&&isRGBAinArr(arr_rgbain,RGBA))//在內外顏色的分界處(左側) 41 if(func_out(lastRGBA)&&func_in(RGBA))//如果上一顏色應該在描邊的外側,同時當前顏色在描邊的內側 42 { 43 var os_left=Math.floor(size_border/2);//偏右 44 var os_right=size_border-os_left; 45 var j_left=j-os_left; 46 var j_right=j+os_right; 47 j_left+=offset_inout; 48 j_right+=offset_inout; 49 for(var k=j_left;k<j_right;k++)//修正偏右 50 { 51 if(k>=0&&k<width) 52 { 53 var index2=(i*240+k)*4; 54 data[index2]=rgb_border.r; 55 data[index2+1]=rgb_border.g; 56 data[index2+2]=rgb_border.b; 57 data[index2+3]=255; 58 } 59 60 } 61 } 62 //else if(isRGBAinArr(arr_rgbaout,RGBA)&&isRGBAinArr(arr_rgbain,lastRGBA))//在內外顏色的分界處(右側) 63 else if(func_out(RGBA)&&func_in(lastRGBA)) 64 { 65 var os_right=Math.floor(size_border/2);//偏左 66 var os_left=size_border-os_right; 67 var j_left=j-os_left; 68 var j_right=j+os_right; 69 j_left-=offset_inout; 70 j_right-=offset_inout; 71 for(var k=j_left+1;k<=j_right;k++)//修正偏左 72 { 73 if(k>=0&&k<width) 74 { 75 var index2 = (i * 240 + k) * 4; 76 data[index2] = rgb_border.r; 77 data[index2 + 1] = rgb_border.g; 78 data[index2 + 2] = rgb_border.b; 79 data[index2 + 3] = 255; 80 } 81 } 82 } 83 } 84 lastRGBA=RGBA; 85 } 86 87 } 88 //然後對於每一列畫素 89 for(var i=0;i<width;i++) 90 { 91 var lastRGBA={}; 92 for(var j=0;j<height;j++)//對於這一列中的每個畫素 93 { 94 var index=(j*240+i)*4; 95 var RGBA={r:data[index],g:data[index+1],b:data[index+2],a:data[index+3]}; 96 //if(!lastRGBA.r&&lastRGBA.r!=0)//如果是第一個畫素 97 if(j==0) 98 { 99 lastRGBA=RGBA; 100 continue; 101 } 102 else 103 { 104 //if(isRGBAinArr(arr_rgbaout,lastRGBA)&&isRGBAinArr(arr_rgbain,RGBA))//在內外顏色的分界處(左側) 105 if(func_out(lastRGBA)&&func_in(RGBA)) 106 { 107 var os_up=Math.floor(size_border/2);//偏下 108 var os_down=size_border-os_up; 109 var j_up=j-os_down; 110 var j_down=j+os_right; 111 j_up+=offset_inout; 112 j_down+=offset_inout; 113 for(var k=j_up;k<j_down;k++)//不修正偏下 114 { 115 if(k>=0&&k<height) 116 { 117 var index2=(k*240+i)*4; 118 data[index2]=rgb_border.r; 119 data[index2+1]=rgb_border.g; 120 data[index2+2]=rgb_border.b; 121 data[index2+3]=255; 122 } 123 124 } 125 } 126 //else if(isRGBAinArr(arr_rgbaout,RGBA)&&isRGBAinArr(arr_rgbain,lastRGBA))//在內外顏色的分界處(右側) 127 else if(func_out(RGBA)&&func_in(lastRGBA)) 128 {//下面應該是忘了改變數名 129 var os_right=Math.floor(size_border/2);//偏左 130 var os_left=size_border-os_right; 131 var j_left=j-os_left; 132 var j_right=j+os_right; 133 j_left-=offset_inout; 134 j_right-=offset_inout; 135 for(var k=j_left;k<j_right;k++)//修正偏左 136 { 137 if(k>=0&&k<height) 138 { 139 var index2 = (k * 240 + i) * 4; 140 data[index2] = rgb_border.r; 141 data[index2 + 1] = rgb_border.g; 142 data[index2 + 2] = rgb_border.b; 143 data[index2 + 3] = 255; 144 } 145 } 146 } 147 } 148 lastRGBA=RGBA; 149 } 150 151 } 152 }
這裡橫豎遍歷所有畫素,在棋子輪廓內外邊界處繪製描邊,演算法細節可能較難以想象,建議親自除錯實驗。使用這種方法繪製的描邊可能比較粗糙。
4、為棋子建立不同狀態的動畫幀
這裡以生命值變化為例:
用棋子“填充度”的降低表示棋子生命值的減少,影象生成演算法如下:
1 console.log("開始生成健康狀態圖片"); 2 /*關於邊界,因為縱向體現狀態比例,所以最上邊和最下邊是必然存在的,用最上邊和最下邊之間的區域分割狀態比例 3 ,然後再根據邊框寬度畫其他的普通邊,考慮到空洞的情況,縱向和橫向的普通邊數量是不確定的 4 -》描邊的操作應該在前一步進行!!??*/ 5 6 i_min+=size_border; 7 i_max-=size_border; 8 var i_height=i_max-i_min; 9 //接下來把它畫在1800*1800的圖片上(設為2048*2048可能獲得更高效能和清晰度,但要求每個單元圖片尺寸也必須是2的整數次冪,比如256*256),分為橫5豎5最多25個狀態 10 /*can_res.style.width=2048+"px"; 11 can_res.width=2048; 12 can_res.style.height=2048+"px"; 13 can_res.height=2048;*/ 14 can_res.style.width=1800+"px"; 15 can_res.width=1800; 16 can_res.style.height=1800+"px"; 17 can_res.height=1800; 18 var con_res=can_res.getContext("2d"); 19 //return; 20 //var data=data_back.data; 21 for(var h=10;h>=0;h--)//健康度狀態分十一個階段遞減 22 { 23 console.log("生成"+h+"/"+10+"的圖片") 24 var int_x=Math.floor((10-h)%5); 25 var int_y=Math.floor((10-h)/5); 26 if(h==10) 27 { 28 con_res.putImageData(data_back,int_x*360+60,int_y*360); 29 } 30 else 31 { 32 var i_up=Math.floor(i_max-i_height*((h+1)/10));//i偏低,取畫素整體偏上 33 var i_down=Math.floor(i_max-i_height*((h)/10)+1); 34 for(var i=i_up;i<i_down;i++)//對於每一行畫素 35 { 36 var j_left=0,j_right=0; 37 for(var j=0;j<240;j++) 38 { 39 var index=(i*240+j)*4; 40 if(data[index]==0&&data[index+1]==0&&data[index+2]==0) 41 { 42 if(j_left==0) 43 { 44 j_left=j; 45 data[index]=0; 46 data[index+1]=255; 47 data[index+2]=0; 48 data[index+3]=0;//將畫素不透明度設為0 49 } 50 else 51 { 52 data[index]=0; 53 data[index+1]=255; 54 data[index+2]=0; 55 data[index+3]=0; 56 j_right=j; 57 } 58 } 59 } 60 /*if(j_right>0) 61 { 62 var index=(i*240+j_right)*4; 63 data[index]=0; 64 data[index+1]=0; 65 data[index+2]=0; 66 data[index+3]=255; 67 }*/ 68 69 70 } 71 //描邊 72 73 con_res.putImageData(data_back,int_x*360+60,int_y*360); 74 //putImageData時完全透明的rgb通道將被丟棄??!! 75 } 76 77 78 }
5、新增“被破壞”動畫幀
實現思路是在棋子上繪製不斷增大的透明圓表示棋子的消逝,需要注意的是因為谷歌瀏覽器無法精確處理半透明計算,所以考慮到以後可能需要繪製半透明的“消逝圓”的情況,先用不透明綠色繪製消逝圓,然後統一把綠色替換為具有精確透明度的顏色。實現程式碼如下:
1 //接下來新增5幀柵格式的退出動畫 2 for(var h=1;h<=5;h++) 3 { 4 var int_x=Math.floor((10+h)%5); 5 var int_y=Math.floor((10+h)/5); 6 con_res.putImageData(data_back,int_x*360+60,int_y*360); 7 con_res.fillStyle="rgba(0,255,0,1)";//考慮到對半透明的檢查,在show圖片時可以先繪製一個綠屏背景!! 8 con_res.lineWidth=0; 9 for(var i=0;i<4;i++) 10 { 11 for(var j=0;j<6;j++) 12 { 13 con_res.beginPath(); 14 con_res.arc(int_x*360+60+30+i*60,int_y*360+30+j*60,6*h,0,Math.PI*2); 15 con_res.fill();//這個方法不能正常呈現a通道 16 } 17 18 } 19 } 20 //將綠幕換成透明 21 22 var data_res=con_res.getImageData(0,0,1800,1800);// 23 var len=1800*1800*4; 24 var datar=data_res.data; 25 for(var i=0;i<len;i+=4) 26 {//這個迴圈內加斷點會導致運算超時 27 if(datar[i]==0&&datar[i+1]==255&&datar[i+2]==0) 28 { 29 datar[i+1]=0; 30 datar[i+3]=0; 31 } 32 } 33 con_res.putImageData(data_res,0,0);
6、使用
經過前面的操作我們得到了棋子“兵”的精靈動畫圖片:
使用相同方法,我們可以得到其他五種棋子的精靈動畫圖片,或者新增更多的精靈動畫幀。我們可以在Babylon.js之類WebGL引擎中使用這些精靈動畫圖片建立精靈動畫,可以在這裡找到Babylon.js的精靈動畫文件:舊版文件:https://ljzc002.github.io/BABYLON101/14Sprites%20-%20Babylon.js%20Documentation.htm,新版文件:https://doc.babylonjs.com/divingDeeper/sprites/sprite_map_animations。(4.2版又有了很多新改變,也許要再次開始文件翻譯工作了)
&n