實驗目標:建立大量物件(萬級),為每個物件設定自身邏輯,并實作物件之間的互動,以原生DOM為渲染方式,主干在于物件邏輯,可根據需求換用其他渲染方式,
一、html舞臺:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>比較整體重繪和移動重繪的效率</title> 6 <link href="../../CSS/newland.css" rel="stylesheet"> 7 <style> 8 div{position: absolute} 9 #div_allbase,#div_map{background-color: darkseagreen} 10 .div_unit{background-repeat: no-repeat;background-size: 100% 100%} 11 </style> 12 <script src="../../JS/MYLIB/newland.js"></script> 13 <script src="./ais.js"></script> 14 <script src="./dos.js"></script> 15 <script src="./commands.js"></script> 16 <script src="./vectorTools.js"></script> 17 </head> 18 <body> 19 <div id="div_allbase" style="width: 100%;height: 100%"> 20 <div id="div_map" ></div> 21 <div id="fps" style="z-index: 302;position:fixed"></div> 22 <div id="div_console" style="z-index: 302;position: fixed;height: 60px;width: 100%;bottom:0px;background-color: #cccccc"> 23 <div id="div_minimap" style="position:fixed;height:60px;width: 80px;left:0px;bottom:0px;background-color: darkseagreen;overflow: hidden"> 24 <div id="div_miniview" style="position:absolute;background-color: #ffffff"></div> 25 </div> 26 <button class="btn_console" onclick="changeView('上')">上</button> 27 <button class="btn_console" onclick="changeView('下')">下</button> 28 <button class="btn_console" onclick="changeView('左')">左</button> 29 <button class="btn_console" onclick="changeView('右')">右</button> 30 <button class="btn_console" onclick="changeView('放大')">放大</button> 31 <button class="btn_console" onclick="changeView('縮小')">縮小</button> 32 <br> 33 <button class="btn_console" onclick="runOneStep()">步進</button> 34 <button class="btn_console" onclick="runLoop()">自動</button> 35 <button class="btn_console" onclick="stopRunLoop()">暫停</button> 36 </div> 37 38 </div> 39 </body> 40 <script> 41 //主體程式入口 42 </script> 43 </html>
其中“div_map”是大量單位的繪制區域,“fps”是顯示幀率的標簽沒有使用,“div_console”是位于螢屏底部的控制區域,其中包括一些控制場景運行的按鈕和一個顯示視窗位置的迷你地圖,
二、場景初始化
1、初始化前的準備:
1 window.onload=beforeInit; 2 function beforeInit() 3 { 4 //這里可以做一些運行環境檢測和渲染方式選擇操作 5 Init(); 6 } 7 var obj_units={};//用id索引所有單位 8 var obj_unitclassed={};//用class索引所有單位型別 9 var arr_part=[];//多重陣列,地圖分塊,優化單位查找速度,最下層是陣列 10 var arr_partowner=[];//多重陣列,用來分塊優化勢力占據,最下層是物件 11 var obj_owners={ 12 red:{color:"red",name:"red",arr_unit:[],countAlive:0}, 13 blue:{color:"blue",name:"blue",arr_unit:[],countAlive:0} 14 }//所有勢力 15 var div_allbase,div_map; 16 var mapWidth=4096*2,mapHeight=3072*2,partSizeX=200,partSizeY=200;//地圖寬度、地圖高度、每個地圖分塊的尺寸 17 var flag_runningstate="beforeStart";//系統總體運行狀態(未用到) 18 var flag_autorun=false;//是否自動運行,默認非自動運行,點擊一次“步進”按鈕計算一次, 19 function Init() 20 { 21 //初始化代碼 22 }
其中arr_part和arr_partowner用來把地圖分成多塊,這樣一個單位在尋找其他單位時就可以從臨近的小塊找起,不必遍歷地圖上的所有單位,把這樣的陣列稱為“索引陣列”,
2、初始化地圖
1 //InitMap 2 div_allbase=document.getElementById("div_allbase"); 3 div_map=document.getElementById("div_map"); 4 div_map.style.width=mapWidth+"px"; 5 div_map.style.height=mapHeight+"px"; 6 var partCountX=Math.ceil(mapWidth/partSizeX); 7 var partCountY=Math.ceil(mapHeight/partSizeY); 8 for(var i=0;i<partCountX;i++)//為地圖上的每個區域分配一個陣列元素 9 { 10 var arr=[]; 11 for(var j=0;j<partCountY;j++) 12 { 13 arr.push([]); 14 } 15 arr_part.push(arr); 16 17 var arr=[]; 18 for(var j=0;j<partCountY;j++) 19 { 20 arr.push({}); 21 } 22 arr_partowner.push(arr); 23 }
3、初始化控制按鈕
1 //InitUI 2 var arr_btn=document.getElementsByClassName("btn_console"); 3 arr_btn[0].onclick=function(){changeView('上')}; 4 arr_btn[1].onclick=function(){changeView('下')}; 5 arr_btn[2].onclick=function(){changeView('左')}; 6 arr_btn[3].onclick=function(){changeView('右')}; 7 arr_btn[4].onclick=function(){changeView('放大')}; 8 arr_btn[5].onclick=function(){changeView('縮小')}; 9 var div_miniview=document.getElementById("div_miniview"); 10 div_miniview.style.width=80*(window.innerWidth/mapWidth)/Math.pow(2,zoomRate)+"px"; 11 div_miniview.style.height=60*(window.innerHeight/mapHeight)/Math.pow(2,zoomRate)+"px"; 12 window.onresize=function(){ 13 var div_miniview=document.getElementById("div_miniview"); 14 div_miniview.style.width=80*(window.innerWidth/mapWidth)/Math.pow(2,zoomRate)+"px"; 15 div_miniview.style.height=60*(window.innerHeight/mapHeight)/Math.pow(2,zoomRate)+"px"; 16 }
其中“zoomRate”是地圖的縮放比例,“div_miniview”在迷你地圖里表示可視區域,當縮放比例放大時,單位變大,視窗中可見的單位變少,迷你地圖的可視區域變小,
4、初始化單位型別:
1 //InitUnitCalss 2 var Yt=function(p){//構造野兔類,并且為它設定一些屬性 3 this.className="Yt"; 4 this.hp=10; 5 this.mp=0; 6 this.sp=10; 7 this.at=2; 8 this.df=1; 9 this.clipSize=20;//碰撞尺寸 10 this.nearAttRange=20;//近戰攻擊范圍 11 this.pic="./yetu.jpg"; 12 this.obj_skills={};//技能串列 13 this.left=p.left;//位置 14 this.top=p.top; 15 this.id=p.id; 16 this.owner=p.owner;//所屬勢力 17 this.doing="standing";//正在做的事 18 this.wanting="waiting";//想要做的事 19 this.being="free";//正在遭受 20 this.speed=50;//移動速度 21 this.view=500;//視野 22 } 23 Yt.prototype.render=function(){//渲染方法 24 if(this.doing=="dead") 25 { 26 return "<div class='div_unit' " + 27 "style='background-color:black;" + 28 "width:"+this.clipSize+"px ;height:"+this.clipSize+"px;left:"+this.left+"px;top:"+this.top+"px;" + 29 "border:2px solid "+this.owner.color+"'>" + 30 "</div>" 31 } 32 if(zoomRate>=2) 33 { 34 return "<div class='div_unit' " + 35 "style='background-image: url("+this.pic+");" + 36 "width:"+this.clipSize+"px ;height:"+this.clipSize+"px;left:"+this.left+"px;top:"+this.top+"px;" + 37 "border:2px solid "+this.owner.color+"'>" + 38 "<div style='background: inherit;width: 100%;height:100%;zoom: 0.1;font-size: 12px;overflow: hidden;color:"+this.owner.color+"'>" + 39 "<div style='top:20px'>doing:"+this.doing+"</div>" + 40 "<div style='top:40px'>wanting:"+this.wanting+"</div>" + 41 "<div style='top:60px'>being:"+this.being+"</div>" + 42 "</div>" + 43 "</div>" 44 } 45 else if(zoomRate>=-1) 46 { 47 return "<div class='div_unit' " + 48 "style='background-image: url("+this.pic+");" + 49 "width:"+this.clipSize+"px ;height:"+this.clipSize+"px;left:"+this.left+"px;top:"+this.top+"px;" + 50 "border:2px solid "+this.owner.color+"'>" + 51 "</div>" 52 } 53 else 54 { 55 return "<div class='div_unit' " + 56 "style='" + 57 "width:"+this.clipSize+"px ;height:"+this.clipSize+"px;left:"+this.left+"px;top:"+this.top+"px;" + 58 "border:2px solid "+this.owner.color+"'>" + 59 "</div>" 60 } 61 62 } 63 Yt.prototype.think=obj_ais.近戰戰士;//思考方式 64 Yt.prototype.do=obj_dos.DOM;//渲染方式,2d網頁標簽的渲染 65 obj_unitclassed.Yt=Yt;
這里根據當前縮放比例的不同為野兔設定了不同的渲染方式,縮放比例大時顯示更多細節,其中“pic”屬性是代表野兔的圖片:

5、初始化單位:
1 //InitUnit 2 for(var i=0;i<10000;i++)//隨機建立10000只野兔 3 { 4 var obj_yt=new obj_unitclassed.Yt({left:newland.RandomBetween(500,mapWidth-500),top:newland.RandomBetween(500,mapHeight-500) 5 ,id:"yt_"+i,owner:newland.RandomChooseFromObj(obj_owners)}); 6 obj_units[obj_yt.id]=obj_yt; 7 var arr_unit=obj_owners[obj_yt.owner.name].arr_unit; 8 obj_yt.ownerNumber=arr_unit.length; 9 arr_unit.push(obj_yt); 10 } 11 obj_owners.red.countAlive=obj_owners.red.arr_unit.length; 12 obj_owners.blue.countAlive=obj_owners.blue.arr_unit.length; 13 列方陣("blue",{left:2000,top:4000},Math.PI/6,100,20,obj_owners); 14 列方陣("red",{left:6000,top:4000},Math.PI*7/6,100,20,obj_owners); 15 for(var key in obj_units) 16 { 17 var obj_yt=obj_units[key]; 18 obj_yt.left=obj_yt.target[0].left; 19 obj_yt.top=obj_yt.target[0].top; 20 obj_yt.target=[]; 21 obj_yt.wanting="waiting"; 22 23 obj_yt.x=Math.floor(obj_yt.left/partSizeX); 24 obj_yt.y=Math.floor(obj_yt.top/partSizeY); 25 arr_part[obj_yt.x][obj_yt.y].push(obj_yt); 26 var obj_temp=arr_partowner[obj_yt.x][obj_yt.y]; 27 if(!obj_temp[obj_yt.owner.name]) 28 { 29 obj_temp[obj_yt.owner.name]=1; 30 } 31 else 32 { 33 obj_temp[obj_yt.owner.name]++; 34 } 35 }
其中“列方陣”方法是寫在command.js中的一個“命令方法”,可以在程式運行時執行這些命令方法改變單位的wanting屬性,進而引導單位接下來的行為,command.js內容如下:
1 function 列方陣(ownerName,pos,angle,col,dis,obj_owners)//勢力名、方陣左上角位置、方陣從right方向起順時針旋轉角度、方陣每行最大人數(也就是最大列數)、單位間距、勢力串列 2 { 3 var arr_unit=obj_owners[ownerName].arr_unit; 4 var len=arr_unit.length; 5 for(var i=0;i<len;i++) 6 { 7 var unit=arr_unit[i]; 8 if(unit.doing!="dead"&&unit.doing!="unconscious") 9 { 10 unit.wanting="lineup";//單位想要去排隊 11 var left0=(i%col)*dis; 12 var top0=Math.floor(i/col)*dis; 13 var r0=Math.pow(left0*left0+top0*top0,0.5); 14 var angle0 15 if(left0==0) 16 { 17 angle0=Math.PI/2; 18 } 19 else 20 { 21 angle0=Math.atan(top0/left0); 22 } 23 var angle1=angle0+angle; 24 var left1=Math.max(pos.left+r0*Math.cos(angle1),0); 25 var top1=Math.max(pos.top+r0*Math.sin(angle1)); 26 unit.target=[{left:left1,top:top1}];//單位想要去這里 27 } 28 } 29 } 30 function 自由沖鋒(ownerName,obj_owners,targetName){//為每個單位選擇單獨的目標最少需要消耗十幾毫秒(加trycatch的情況下),去掉trycatch速度提升百倍 31 var arr_unit=obj_owners[ownerName].arr_unit; 32 var len=arr_unit.length; 33 for(var i=0;i<len;i++) 34 { 35 var unit=arr_unit[i]; 36 if(unit.doing!="dead"&&unit.doing!="unconscious") 37 { 38 unit.wanting="freecharge"; 39 unit.target=[targetName] 40 } 41 } 42 }
可以看到“列方陣”方法為勢力中的所有物件分配了一個方陣位置,然后初始化方法直接把target設為單位當前位置,這樣10000只野兔剛出場時就是排號方陣的,
接下來計算每個單位屬于arr_part和arr_partowner的哪個小塊,
6、初始化渲染和渲染回圈:
1 //TestRender 2 div_map.style.zoom=Math.pow(2,zoomRate)+'';//對單位顯示區應用地圖縮放 3 renderMap(); 4 自由沖鋒("blue",obj_owners,"red"); 5 自由沖鋒("red",obj_owners,"blue"); 6 7 //InitRenderLoop 8 Loop();
其中renderMap方法用來繪制單位顯示區:
function renderMap(){ //繪制整個地圖,考慮到運行效率,只render當前顯示范圍內的!!!!,那么移動時也要重新繪制!!?? var innerWidth=window.innerWidth; var innerHeight=window.innerHeight; var left=parseInt(div_map.style.left||0); var top=parseInt(div_map.style.top||0); var y1=(-top);///Math.pow(2,zoomRate);//畫面縮小時,同樣的地圖位移意味著更多的距離 var x1=(-left);///Math.pow(2,zoomRate); var y2=y1+innerHeight/Math.pow(2,zoomRate); var x2=x1+innerWidth/Math.pow(2,zoomRate);//以上找到了可視區域 var arr_temp=[]; for(var key in obj_units) { var unit=obj_units[key]; if(unit.left>(x1-unit.clipSize)&&unit.left<x2&&unit.top>(y1-unit.clipSize)&&unit.top<y2) {//繪制可視區域內的單位 arr_temp.push(obj_units[key].render()); } } div_map.innerHTML=arr_temp.join("");//一次性繪制 }
繪制顯示區有兩種思路,一是一次性繪制顯示區中的所有單位,這樣移動視口時就不必重新繪制單位;而是只繪制顯示在顯示區域內的單位,視口改變時臨時繪制新顯示出的單位,經過實驗,在當前配置下第二種方式的繪制速度遠大于第一種,
繪制代碼的最后,把每個單位的render方法回傳的html文本合并起來一次性繪制,這樣做的繪制速度遠遠高于分別繪制每一單位(數百倍到上千倍),其原理在于每條修改innerHTML或createElement的操作都會觸發一次js引擎到瀏覽器渲染引擎的通信,之后瀏覽器渲染引擎將對頁面進行繪制,而js引擎將掛起等待繪制完成,并且此程序中的js掛起時間遠大于js計算和通信時間,一次性繪制則可以節省掉大量的頁面繪制次數,能夠大大提升渲染速度,
順便記錄這種一次性繪制與React“虛擬DOM”的關系——事實上React的底層繪制仍然是使用createElement建立每個標簽,并沒有實作一次性繪制,而虛擬DOM的特點在于“把所有createElement集中起來一起執行,在統一執行前,合并對同一標簽的反復修改并檢查虛擬DOM有無變化,如虛擬DOM無變化則不執行”,以此減少繪制次數,
可以用谷歌瀏覽器的performance功能配合以下程式測驗這一區別:(以下內容缺乏大量充分試驗,并且與本文主干無關)
組件:
1 import { Component, Fragment } from "react"; 2 3 //測驗setState和傳統dom操作的性能差距 4 class App extends Component { 5 constructor(props){ 6 super(); 7 this.state={ 8 arr:[], 9 } 10 11 } 12 onClick1=()=>{ 13 console.log(new Date().getTime()) 14 var arr=[]; 15 for(var i=0;i<10000;i++) 16 { 17 arr.push({key:i}); 18 } 19 this.setState({arr:arr}); 20 } 21 componentDidUpdate() 22 { 23 console.log(new Date().getTime()) 24 } 25 onClick2=()=>{ 26 console.log(new Date().getTime()); 27 var div_allbase=document.getElementById("div_allbase"); 28 for(var i=0;i<10000;i++) 29 { 30 var div=document.createElement("div") 31 div.innerHTML=i; 32 div_allbase.appendChild(div); 33 } 34 console.log(new Date().getTime()); 35 } 36 onClick3=()=>{ 37 console.log(new Date().getTime()); 38 var div_allbase=document.getElementById("div_allbase"); 39 var div0=document.createElement("div") 40 for(var i=0;i<10000;i++) 41 { 42 var div=document.createElement("div")//這一步還是有多余的js引擎到dom引擎的通信 43 div.innerHTML=i; 44 div0.appendChild(div); 45 } 46 div_allbase.appendChild(div0); 47 console.log(new Date().getTime()); 48 } 49 onClick4=()=>{ 50 console.log(new Date().getTime()); 51 var div_allbase=document.getElementById("div_allbase"); 52 var str=""; 53 var arr=[]; 54 for(var i=0;i<10000;i++) 55 { 56 arr.push("<div>"+i+"</div>"); 57 } 58 str=arr.join(""); 59 div_allbase.innerHTML=str; 60 console.log(new Date().getTime()); 61 } 62 render() { 63 const { count, price,show } = this.state; 64 return <Fragment> 65 <button onClick={()=>this.onClick1()}>setState方法</button> 66 <button onClick={()=>this.onClick2()}>Dom方法</button> 67 <button onClick={()=>this.onClick3()}>一次添加方法</button> 68 <button onClick={()=>this.onClick4()}>innerHTML方法</button> 69 <div id={"div_allbase"} style={{}}> 70 {this.state.arr.map(d=><div key={d.key}>{d.key}</div>) } 71 </div> 72 </Fragment> 73 } 74 } 75 76 export default App;View Code
index.js
1 import React from 'react'; 2 import ReactDOM from 'react-dom'; 3 import './index.css'; 4 import App from './test2/App4'; 5 6 ReactDOM.render( 7 <React.StrictMode> 8 <App /> 9 </React.StrictMode>, 10 document.getElementById('root') 11 );View Code
package.json:
1 { 2 "name": "my-app4", 3 "version": "0.1.0", 4 "private": true, 5 "dependencies": { 6 "@testing-library/jest-dom": "^5.11.4", 7 "@testing-library/react": "^11.1.0", 8 "@testing-library/user-event": "^12.1.10", 9 "antd": "^4.9.2", 10 "babylonjs": "^4.2.0", 11 "codeflask": "^1.4.1", 12 "moment": "^2.29.1", 13 "react": "^17.0.1", 14 "react-dom": "^17.0.1", 15 "react-router-dom": "^5.2.0", 16 "react-scripts": "4.0.1", 17 "web-vitals": "^0.2.4" 18 }, 19 "scripts": { 20 "start": "react-scripts start", 21 "build": "react-scripts build", 22 "test": "react-scripts test", 23 "eject": "react-scripts eject" 24 }, 25 "eslintConfig": { 26 "extends": [ 27 "react-app", 28 "react-app/jest" 29 ] 30 }, 31 "browserslist": { 32 "production": [ 33 ">0.2%", 34 "not dead", 35 "not op_mini all" 36 ], 37 "development": [ 38 "last 1 chrome version", 39 "last 1 firefox version", 40 "last 1 safari version" 41 ] 42 } 43 }View Code
結果,單純從一次渲染大量標簽的速度來看React的setState<每個標簽單獨appendChild<先將大量標簽放在一個容器中,然后把容器append到body<使用innerHTML一次性修改,
以下是分別渲染100000個div時的耗時情況:

7、視口移動和縮放
1 var sizeStep=250; 2 var zoomRate=-3; 3 var scrollTop=0,scrollLeft=0; 4 function changeView(type){ 5 var div_miniview=document.getElementById("div_miniview"); 6 if(type=="上") 7 { 8 scrollTop=scrollTop+sizeStep/Math.pow(2,zoomRate);//畫面放大時,卷屏更慢 9 if(scrollTop>0) 10 { 11 scrollTop=0; 12 13 } 14 div_map.style.top=scrollTop+"px"; 15 renderMap(); 16 div_miniview.style.top=-60*(scrollTop/mapHeight)+"px"; 17 } 18 else if(type=="下") 19 { 20 scrollTop=scrollTop-sizeStep/Math.pow(2,zoomRate); 21 if(scrollTop<-mapHeight) 22 { 23 scrollTop=-mapHeight; 24 25 } 26 div_map.style.top=scrollTop+"px"; 27 renderMap(); 28 div_miniview.style.top=-60*(scrollTop/mapHeight)+"px"; 29 } 30 else if(type=="左") 31 { 32 scrollLeft=scrollLeft+sizeStep/Math.pow(2,zoomRate); 33 if(scrollLeft>0) 34 { 35 scrollLeft=0; 36 } 37 div_map.style.left=scrollLeft+"px"; 38 renderMap(); 39 div_miniview.style.left=-80*(scrollLeft/mapWidth)+"px"; 40 } 41 else if(type=="右") 42 { 43 scrollLeft=scrollLeft-sizeStep/Math.pow(2,zoomRate); 44 if(scrollLeft<-mapWidth) 45 { 46 scrollLeft=-mapWidth; 47 } 48 div_map.style.left=scrollLeft+"px"; 49 renderMap(); 50 div_miniview.style.left=-80*(scrollLeft/mapWidth)+"px"; 51 } 52 else if(type=="放大") 53 { 54 zoomRate++; 55 if(zoomRate>3) 56 { 57 zoomRate=3; 58 } 59 60 div_map.style.zoom=Math.pow(2,zoomRate); 61 renderMap(); 62 //zoomRate=zoomRate*2; 63 //div_map.style.zoom=zoomRate; 64 div_miniview.style.width=80*(window.innerWidth/mapWidth)/Math.pow(2,zoomRate)+"px"; 65 div_miniview.style.height=60*(window.innerHeight/mapHeight)/Math.pow(2,zoomRate)+"px"; 66 } 67 else if(type=="縮小") 68 { 69 zoomRate--; 70 if(zoomRate<-3) 71 { 72 zoomRate=-3; 73 } 74 div_map.style.zoom=Math.pow(2,zoomRate); 75 renderMap(); 76 div_miniview.style.width=80*(window.innerWidth/mapWidth)/Math.pow(2,zoomRate)+"px"; 77 div_miniview.style.height=60*(window.innerHeight/mapHeight)/Math.pow(2,zoomRate)+"px"; 78 // zoomRate=zoomRate/2; 79 // div_map.style.zoom=zoomRate; 80 } 81 }View Code
主要是html標簽的樣式操作,
8、渲染回圈
1 function runOneStep(){//遍歷每個unit并決定它要做的事,runLoop也要呼叫這個方法,暫時把思考、行動、渲染放在同步的幀里,思考頻率、移動速度、單位大小要相互和諧,以正常移動避免碰撞為標準 2 for(var key in obj_units)//思考 3 { 4 var unit=obj_units[key]; 5 if(unit.doing!="dead"&&unit.doing!="unconscious") 6 { 7 unit.think(unit,obj_units,arr_part); 8 } 9 } 10 for(var key in obj_units)//行動 11 { 12 var unit=obj_units[key]; 13 if(unit.doing!="dead"&&unit.doing!="unconscious") 14 { 15 unit.do(unit); 16 } 17 } 18 renderMap();//渲染 19 } 20 function runLoop(){ 21 flag_autorun=true; 22 } 23 function stopRunLoop(){ 24 flag_autorun=false; 25 } 26 var lastframe=new Date().getTime(); 27 function Loop() 28 { 29 if(flag_autorun) 30 { 31 runOneStep(); 32 var thisframe=new Date().getTime(); 33 console.log(thisframe-lastframe,"red:"+obj_owners.red.countAlive,"blue:"+obj_owners.blue.countAlive); 34 lastframe=thisframe; 35 } 36 window.requestAnimationFrame(function(){Loop()}); 37 }
事實上可思考的物件存在三個回圈“思考回圈”——比如最小間隔1秒考慮一次接下來做什么,“行動回圈”——比如受到持續傷害時最小間隔0.2秒修改生命值,“渲染回圈”——比如播放60幀每秒的模型影片,這里為了省事把三個回圈合在一起執行,
三、思考
ais.js:
1 var obj_ais={};//ai串列 2 obj_ais.近戰戰士=function(unit,obj_units,arr_part){//通過原型方法呼叫,那么這里的this應該是誰?? 3 if(unit.wanting=="lineup")//如果單位現在想列隊,排隊也有兩種思路,一是靠臨近人員自組織,二是獲取所有單位統籌規劃 4 {//這時應該有一個target引數指明單位要列隊的地點,這個地點應該是隊形中心還是單位的實際點?? 5 //應該在列隊開始時就為每個單位設定精確目標點,還是在運動中實時縮小范圍?還是每個單位都在創建時就分配一個隊伍位置?? 6 var pos_target=unit.target[0]; 7 if(unit2isat(unit,pos_target)) 8 {//如果單位已經到位 9 unit.wanting="waiting"; 10 unit.doing="standing"; 11 unit.target=[]; 12 } 13 else 14 { 15 unit.doing="walk"; 16 if(unit2distance(unit,pos_target)<unit.speed)//如果目標已經在一次思考時間的移動范圍內 17 { 18 unit.nextpos=pos_target; 19 } 20 else 21 {//計算下一步的位置 22 unit.nextpos=unit2add(unit2times(unit2normal(unit2substract(unit,pos_target)),unit.speed),unit); 23 } 24 } 25 26 27 28 29 } 30 if(unit.wanting=="freecharge") //沖鋒程序中的每次思考都要重新規劃目標 31 { 32 if(!unit.aimAt||unit.aimAt.doing=="dead")//如果還沒有瞄準的目標,或者瞄準的目標已經死亡,則要尋找一個瞄準目標 33 { 34 var arr_res=findNearUnit(arr_part,0,20,"nearest-target-notdead-onlyone",unit.x,unit.y,unit,arr_partowner);//找到了一個目標 35 unit.aimAt=arr_res[0]; 36 } 37 if(unit.aimAt) 38 { 39 var dis=unit2distance(unit,unit.aimAt); 40 if(dis<unit.nearAttRange)//如果進入射程 41 { 42 unit.doing="nearattack"//近戰普通攻擊 43 } 44 else 45 { 46 47 if(dis<unit.speed)//如果目標已經在一次思考時間的移動范圍內,移動要留下攻擊目標的半徑 48 { 49 unit.doing="chargeattack";//沖鋒攻擊 50 unit.nextpos=unit2substract(unit2times(unit2normal(unit2substract(unit,unit.aimAt)),unit.aimAt.clipSize/2),unit.aimAt,); 51 } 52 else 53 { 54 unit.doing="walk"; 55 unit.nextpos=unit2add(unit2times(unit2normal(unit2substract(unit,unit.aimAt)),unit.speed),unit); 56 } 57 } 58 } 59 else 60 { 61 unit.doing="standing";//如果已經找不到敵人則恢復靜默狀態 62 } 63 } 64 }
這里建立了一個叫做“近戰戰士”的思考方法,接著根據單位wanting屬性的不同為他設定不同的doing、target、nextpos等屬性,
四、行動
dos.js
1 var obj_dos={} 2 obj_dos.DOM=function(unit){ 3 if(unit.doing=="walk") 4 { 5 unit.left=Math.max(unit.nextpos.left,0); 6 unit.top=Math.max(unit.nextpos.top,0); 7 8 var x=Math.floor(unit.left/partSizeX); 9 var y=Math.floor(unit.top/partSizeY); 10 if(x!=unit.x||y!=unit.y)//更新索引位置 11 { 12 unit.x=x; 13 unit.y=y; 14 updatePart(unit,arr_part,arr_partowner); 15 } 16 } 17 else if(unit.doing=="nearattack") 18 { 19 var unitAimAt=unit.aimAt; 20 if(unitAimAt.doing!="dead") 21 { 22 if(unitAimAt.being=="free") 23 { 24 unitAimAt.being="hp-"+unit.at; 25 26 } 27 else 28 { 29 unitAimAt.being+=";hp-"+unit.at; 30 } 31 unitAimAt.hp-=unit.at; 32 if(unitAimAt.hp<1) 33 { 34 unitAimAt.doing="dead"; 35 obj_owners[unitAimAt.owner.name].countAlive--; 36 arr_partowner[unitAimAt.x][unitAimAt.y][unitAimAt.owner.name]--; 37 } 38 } 39 } 40 else if(unit.doing=="chargeattack") 41 { 42 unit.left=Math.max(unit.nextpos.left,0); 43 unit.top=Math.max(unit.nextpos.top,0); 44 var x=Math.floor(unit.left/partSizeX); 45 var y=Math.floor(unit.top/partSizeY); 46 if(x!=unit.x||y!=unit.y)//更新索引位置 47 { 48 unit.x=x; 49 unit.y=y; 50 updatePart(unit,arr_part,arr_partowner); 51 } 52 53 var unitAimAt=unit.aimAt; 54 if(unitAimAt.doing!="dead") 55 { 56 if(unitAimAt.being=="free") 57 { 58 unitAimAt.being="hp-"+unit.at; 59 60 } 61 else 62 { 63 unitAimAt.being+=";hp-"+unit.at; 64 } 65 unitAimAt.hp-=unit.at; 66 if(unitAimAt.hp<1) 67 { 68 unitAimAt.doing="dead"; 69 obj_owners[unitAimAt.owner.name].countAlive--; 70 arr_partowner[unitAimAt.x][unitAimAt.y][unitAimAt.owner.name]--; 71 } 72 } 73 } 74 }
根據單位doing屬性的不同,修改單位自身或其他單位的屬性,
五、平面向量計算與快速單位查找
vectorTools.js:
1 function findNearUnit(arr_part,start,count,type,x,y,unit,arr_partowner) 2 {//以自身為出發點,尋找附近的單位 3 var arr_res=[],arr_find1,arr_find2,arr_find3,arr_find4; 4 if(type=="all")//遍歷所有格找尋所有符合條件的格 5 { 6 for (var i = start; i <= count; i++)//x.y相加的總步數,0就是本格 7 { 8 for (var xStep1 = 0; xStep1 <= i; xStep1++) 9 { 10 var yStep1 = i - Math.abs(xStep1); 11 //var yStep2 = -yStep1; 12 //var xStep2 = -xStep1; 13 //這里要注意處理陣列為null的情況 14 var arr2 = arr_part[x + xStep1];//使用arr_part查找附近單位,避免遍歷所有單位 15 arr_find1 = arr2 ? (arr2[y + yStep1]) : null; 16 arr_find2 = arr2 ? (arr2[y - yStep1]) : null; 17 arr2 = arr_part[x - xStep1]; 18 arr_find3 = arr2 ? (arr2[y + yStep1]) : null; 19 arr_find4 = arr2 ? (arr2[y - yStep1]) : null; 20 if(arr_find1) 21 { 22 arr_res=arr_res.concat(arr_find1); 23 } 24 if(arr_find2) 25 { 26 arr_res=arr_res.concat(arr_find2); 27 } 28 if(arr_find3) 29 { 30 arr_res=arr_res.concat(arr_find3); 31 } 32 if(arr_find4) 33 { 34 arr_res=arr_res.concat(arr_find4); 35 } 36 } 37 } 38 39 } 40 else if(type=="nearest"){//從近向遠遍歷,取并列最近的所有格,之后停止遍歷 41 for (var i = start; i <= count; i++)//x.y相加的總步數,0就是本格 42 { 43 for (var xStep1 = 0; xStep1 <= i; xStep1++) { 44 var yStep1 = i - Math.abs(xStep1); 45 var yStep2 = -yStep1; 46 var xStep2 = -xStep1; 47 //這里要注意處理陣列為null的情況 48 var arr2 = arr_part[x + xStep1]; 49 arr_find1 = arr2 ? (arr2[y + yStep1]) : null; 50 arr_find2 = arr2 ? (arr2[y - yStep1]) : null; 51 arr2 = arr_part[x - xStep1]; 52 arr_find3 = arr2 ? (arr2[y + yStep1]) : null; 53 arr_find4 = arr2 ? (arr2[y - yStep1]) : null; 54 if(arr_find1) 55 { 56 arr_res=arr_res.concat(arr_find1); 57 } 58 if(arr_find2) 59 { 60 arr_res=arr_res.concat(arr_find2); 61 } 62 if(arr_find3) 63 { 64 arr_res=arr_res.concat(arr_find3); 65 } 66 if(arr_find4) 67 { 68 arr_res=arr_res.concat(arr_find4); 69 } 70 } 71 if(arr_res.length>0) 72 { 73 break; 74 } 75 } 76 } 77 else if(type=="nearest-target-notdead-onlyone") 78 {//取最近的、屬于規定勢力的、沒有死亡的、只取一個 79 var arr_res0=[]; 80 var owner_target=unit.target[0]; 81 for (var i = start; i <= count; i++)//x.y相加的總步數,0就是本格 82 { 83 for (var xStep1 = 0; xStep1 <= i; xStep1++) { 84 var yStep1 = i - Math.abs(xStep1); 85 var yStep2 = -yStep1; 86 var xStep2 = -xStep1; 87 //這里要注意處理陣列為null的情況 88 //四個方向隨機選擇 89 //這里要避免單位不斷遍歷身邊的大量己方單位!! 90 //try catch對性能有額外消耗??!! 91 var count2=0; 92 // try{ 93 // count2+=arr_partowner[x + xStep1][y + yStep1][owner_target]||0; 94 // }catch(e){} 95 // try{ 96 // count2+=arr_partowner[x + xStep1][y - yStep1][owner_target]||0; 97 // }catch(e){} 98 // try{ 99 // count2+=arr_partowner[x - xStep1][y + yStep1][owner_target]||0; 100 // }catch(e){} 101 // try{ 102 // count2+=arr_partowner[x - xStep1][y - yStep1][owner_target]||0; 103 // }catch(e){} 104 var arr2=arr_partowner[x + xStep1]; 105 if(arr2) 106 { 107 var obj=arr2[y + yStep1]; 108 if(obj) 109 { 110 count2+=obj[owner_target]||0; 111 } 112 var obj=arr2[y - yStep1]; 113 if(obj) 114 { 115 count2+=obj[owner_target]||0; 116 } 117 } 118 var arr2=arr_partowner[x - xStep1]; 119 if(arr2) 120 { 121 var obj=arr2[y + yStep1]; 122 if(obj) 123 { 124 count2+=obj[owner_target]||0; 125 } 126 var obj=arr2[y - yStep1]; 127 if(obj) 128 { 129 count2+=obj[owner_target]||0; 130 } 131 } 132 if(count2>0)//如果找到了對應目標的領域,則認為一定能找到一個目標??(活的)!! 133 { 134 var arr2 = arr_part[x + xStep1]; 135 arr_find1 = arr2 ? (arr2[y + yStep1]) : []; 136 arr_find2 = arr2 ? (arr2[y - yStep1]) : []; 137 arr2 = arr_part[x - xStep1]; 138 arr_find3 = arr2 ? (arr2[y + yStep1]) : []; 139 arr_find4 = arr2 ? (arr2[y - yStep1]) : []; 140 if(arr_find1) 141 { 142 arr_res0=arr_res0.concat(arr_find1); 143 } 144 if(arr_find2) 145 { 146 arr_res0=arr_res0.concat(arr_find2); 147 } 148 if(arr_find3) 149 { 150 arr_res0=arr_res0.concat(arr_find3); 151 } 152 if(arr_find4) 153 { 154 arr_res0=arr_res0.concat(arr_find4); 155 } 156 var len2=arr_res0.length; 157 var nearestUnit=null; 158 for(var j=0;j<len2;j++) 159 { 160 161 var obj=arr_res0[j]; 162 if(obj.doing!="dead"&&obj.owner.name==owner_target) 163 { 164 165 if(!nearestUnit) 166 { 167 nearestUnit=obj; 168 } 169 else 170 { 171 if(unit2distance(obj,unit)<unit2distance(nearestUnit,unit)){ 172 nearestUnit=obj; 173 } 174 } 175 } 176 } 177 if(nearestUnit) 178 { 179 arr_res.push(nearestUnit); 180 } 181 182 } 183 } 184 if(arr_res.length>0) 185 { 186 //arr_res=[arr_res[newland.RandomChooseFromArray(arr_res)]]; 187 break; 188 } 189 } 190 } 191 return arr_res; 192 }//分隔線 193 function unit2distance(unit1,unit2)//兩點間距離 194 { 195 return Math.pow(Math.pow((unit1.left-unit2.left),2)+Math.pow((unit1.top-unit2.top),2),0.5) 196 } 197 function unit2isat(unit,pos) 198 { 199 if(unit.left==pos.left&&unit.top==pos.top)//如果單位正好處于這個位置 200 { 201 return true; 202 } 203 else 204 { 205 return false; 206 } 207 } 208 function unit2substract(posFrom,posTo)//取兩個二元向量的差向量 209 { 210 var posRes={left:posTo.left-posFrom.left,top:posTo.top-posFrom.top}; 211 return posRes; 212 } 213 function unit2normal(unit)//標準化二元向量 214 { 215 var length=Math.pow(unit.left*unit.left+unit.top*unit.top,0.5); 216 var posRes={left:unit.left/length,top:unit.top/length}; 217 return posRes; 218 } 219 function unit2times(unit,times)//二元向量伸縮 220 { 221 var posRes={left:unit.left*times,top:unit.top*times}; 222 return posRes; 223 } 224 function unit2add(unit1,unit2) 225 { 226 var posRes={left:unit1.left+unit2.left,top:unit1.top+unit2.top}; 227 return posRes; 228 } 229 function isTooNear(unit,arr,dis)//unit與arr中的物件是否過于接近,只要有一個就回傳true 230 { 231 var len=arr.length; 232 if(!dis)//如果沒有規定統一的最近距離, 233 { 234 for(var i=0;i<len;i++) 235 { 236 var obj=arr[i] 237 if(unit2distance(unit,obj)<(unit.clipSize+obj.clipSize)/2) 238 { 239 return true; 240 } 241 } 242 } 243 else 244 { 245 for(var i=0;i<len;i++) 246 { 247 var obj=arr[i] 248 if(unit2distance(unit,obj)<dis) 249 { 250 return true; 251 } 252 } 253 } 254 255 }//分隔線 256 function updatePart(unit,arr_part,arr_partowner){//更新兩個索引陣列 257 var x=unit.x; 258 var y=unit.y; 259 var arr_old=arr_part[x][y]; 260 var len=arr_old.length; 261 for(var i=0;i<len;i++) 262 { 263 if(arr_old[i].id==unit.id) 264 { 265 arr_old.splice(i,1); 266 arr_partowner[x][y][unit.owner.name]--; 267 break; 268 } 269 } 270 if(!arr_part[x]) 271 { 272 arr_part[x]=[]; 273 } 274 if(!arr_part[x][y]) 275 { 276 arr_part[x][y]=[]; 277 } 278 arr_part[x][y].push(unit); 279 if(!arr_partowner[x]) 280 { 281 arr_partowner[x]=[]; 282 } 283 if(!arr_partowner[x][y]) 284 { 285 arr_partowner[x][y]={}; 286 } 287 var obj_temp=arr_partowner[x][y]; 288 if(!obj_temp[unit.owner.name]) 289 { 290 obj_temp[unit.owner.name]=1; 291 } 292 else 293 { 294 obj_temp[unit.owner.name]++; 295 } 296 }
六、總結
以上完成了一個最基礎的多單位思考與互動框架,在此基礎上可以編輯更多種類的單位和更復雜思考方式,但框架尚存在問題,比如缺少單位間碰撞檢測(或多層堆疊限制),這會導致所有單位最終重疊為一點,比如視角控制方式不流暢,比如三個回圈未分離等等,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/251558.html
標籤:JavaScript
上一篇:溫習資料演算法—貪吃蛇
