2021-01-19

网页小实验——在平面空间建立大量“可思考的”对象

实验目标:建立大量对象(万级),为每个对象设置自身逻辑,并实现对象之间的交互,以原生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    else54    {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    else32    {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 angle015    if(left0==0)16    {17     angle0=Math.PI/2;18    }19    else20    {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 //TestRender2   div_map.style.zoom=Math.pow(2,zoomRate)+'';//对单位显示区应用地图缩放3   renderMap();4   自由冲锋("blue",obj_owners,"red");5   自由冲锋("red",obj_owners,"blue");6 7   //InitRenderLoop8   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   else14   {15    unit.doing="walk";16    if(unit2distance(unit,pos_target)<unit.speed)//如果目标已经在一次思考时间的移动范围内17    {18     unit.nextpos=pos_target;19    }20    else21    {//计算下一步的位置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    else45    {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     else53     {54      unit.doing="walk";55      unit.nextpos=unit2add(unit2times(unit2normal(unit2substract(unit,unit.aimAt)),unit.speed),unit);56     }57    }58   }59   else60   {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    else28    {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    else62    {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        else170        {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  else204  {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中的对象是否过于接近,只要有一个就返回true230 {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  else244  {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  else293  {294   obj_temp[unit.owner.name]++;295  }296 }

六、总结

以上完成了一个最基础的多单位思考与交互框架,在此基础上可以编辑更多种类的单位和更复杂思考方式,但框架尚存在问题,比如缺少单位间碰撞检测(或多层堆叠限制),这会导致所有单位最终重叠为一点,比如视角控制方式不流畅,比如三个循环未分离等等。

 









原文转载:http://www.shaoqun.com/a/512228.html

跨境电商:https://www.ikjzd.com/

usps:https://www.ikjzd.com/w/513

lithium:https://www.ikjzd.com/w/2505


实验目标:建立大量对象(万级),为每个对象设置自身逻辑,并实现对象之间的交互,以原生DOM为渲染方式。主干在于对象逻辑,可根据需求换用其他渲染方式。一、html舞台:1<!DOCTYPEhtml>2<htmllang="en">3<head>4<metacharset="UTF-8">5<title>
terapeak:terapeak
zozotown:zozotown
亚马逊红人资源分享:亚马逊红人资源分享
国庆去北戴河旅游住在哪里好?:国庆去北戴河旅游住在哪里好?
亚马逊推出FBA库存新功能!卖家有望申请仓储扩容!:亚马逊推出FBA库存新功能!卖家有望申请仓储扩容!

No comments:

Post a Comment