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


    实验目标:建立大量对象(万级),为每个对象设置自身逻辑,并实现对象之间的交互,以原生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=" 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; 100%;bottom:0px;background-color: #cccccc">
    23         <div id="div_minimap" style="position:fixed;height:60px; 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                     ""+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                     ""+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; 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                     ""+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                     ""+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 }

    六、总结

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

  • 相关阅读:
    C盘与D盘中间有个恢复分区,导致C盘不能扩展卷解决
    Win下,QT控制台无输出解决
    QT与ECharts交互,绘制曲线图
    博客园好看的自定义主题
    Qt5之控件在初始化时就触发了槽函数的问题解决方案
    使用QCustomPlot,跟随鼠标动态显示线上点的值
    QCustomPlot下setTickLabelType()函数在新版本被移除如何解决
    记一次QT使用QAxWidget打开.html文件调用显示离线百度地图不能缩放,自定义图片不能显示解决方法
    使用QPainter绘制汽车仪表盘,动态显示
    QT下使用百度地图js,发送角度值给js使小车根据角度值调整车头方向
  • 原文地址:https://www.cnblogs.com/ljzc002/p/14297419.html
Copyright © 2020-2023  润新知