First, operation effect

1. Build a map with topographic relief and different geomorphic textures:

In the middle of the map is a sunken river, and the two corners are prominent uplands. The uplands and lowlands are connected by sloping channels.

The underwater material is sand, the sand material mesh fits the terrain, and the river material mesh remains horizontal.

2. Place small yellow squares randomly on the map to represent controlled units

The default control is free camera – drag and drop the left mouse button to change the Angle of view, up and down the left button to move; Press V to switch to RTS control, lock the view Angle to 45 degrees overhead, press WASD to move the camera horizontally, and mouse wheel to adjust camera zoom.

3. Left-click and drag the mouse to generate a selection box:

 

When you release the mouse, the selected unit appears white

4. Right-click on the map, select the unit and start pathfinding to the target location

The white dotted line is the expected path of the units, so you can see that the units move in line with the ground and cross the river via a ramp rather than a straight flight. (During long distance navigation, it was found that the expected path of some units was not displayed. Due to limited time, it was not debugged carefully.)

Groups of units can be assigned different destinations, and units will automatically bypass each other when they meet and move on

5. Click the left mouse button to select the unit

 

The above code can be downloaded from https://github.com/ljzc002/ControlRTS, projects using traditional HTML CSS, js reference form building, suggested that the reader has Babylon. Js knowledge and a little ES6 knowledge.

The project structure is as follows:

 

 

 

2. Realize map editing

Createmap2. HTML is the entrance map editor files, set in WebGL recommended reading https://www.cnblogs.com/ljzc002/p/11105496.html for some methods of terrain, the project with the help of some of these ideas, Only the different points will be introduced here, and the repetitive parts will not be repeated. Map editing is not directly related to RTS control, so readers who are not interested in map editing can skip to the next chapter.

1, entry HTML to generate terrain code:

1 var ground1=new FrameGround(); // The "ground class" defined in frameground2.js, 2 var obj_p={3 name:"ground1", 4 Segs_x: Segs_x, 5 Segs_z :segs_z, 6 size_per_x:size_per_x, 7 size_per_z:size_per_z, 8 mat:"mat_grass", 9 }; 10 ground1.init(obj_p); 11 / / ground1 TransVertexGradientlyByDistance (new BABYLON. Vector3 (0, 0, - 50), 30, [[0,14,15], [15, four, five], [1] 30]); 12 obj_ground["ground1"]=ground1; 13 14 cri(); // This is a shorthand for some global methods written in the command-.js file. For example, "cri" is a shorthand for the global command-.refreshisinarea method, which is used to introduce additional code when the program runs. It contains code to determine the scope. 15 ct2 (isInArea1, 3); // Set the height of the vertices in isInArea1 to 3, 16 ct2(isInArea2,-3); 17 18 tc3 (15, 15, - Math. PI / 4,6,3,3,0); // create ramp 19 ct3(70,20, -math.pi /4,6,3,0,-3) at the specified position according to the specified horizontal Angle, length, width, and height; 20 tc3 (45 do - Math. PI / 4,6,3,0, 3); 21 tc3 (20; seven - Math. PI / 4,6,3,0, 3); 22 tc3 (85, 30 - Math. PI / 4,6,3,0,3); 23 tc3 (30, 80 - Math.,6,3 PI / 4, 3, 0). 24 tc3 (55 zhongguo kuangye daxue, - Math.,6,3 PI / 4, 3, 0). Function (vec){28 if(vec. Y <-2) 29 {30 return true; 31 } 32 },ground1.obj_mat.mat_sand,"ground_sand"); 34 var water = new Babylon. WaterMaterial("water", scene, new BABYLON.Vector2(1024, 1024)); 35 water.backFaceCulling = true; 36 water.bumpTexture = new BABYLON.Texture(".. /.. /ASSETS/IMAGE/LANDTYPE/waterbump.png", scene); 37 water.windForce = -5; 38 water. WaveHeight = 0.1; 39 water. BumpHeight = 0.1; 40 water. Where = 0.05; 41 water. ColorBlendFactor = 0.2; 42 water.addToRenderList(skybox); 43 water.addToRenderList(ground1.ground_base); 44 water.addToRenderList(obj_ground.ground_sand.ground_base); 45 46 ground1.MakeLandtype1(function(vec){ 47 if(vec.y<-0) 48 { 49 return true; 50} 51}, 52 ground1.obj_mat. mat_shallowWater // use normal water texture 53 //water, find surface reflection material bug 54,"ground_water",true,-2);Copy the code

The frameground object is initialized and the terrain and ground textures are set using “map edit methods” such as “CT2”. Note that the map edit methods can be run in code or in the browser console while the application is running. You can even use the CRI method to introduce new map editing methods at any time.

“WaterMaterial” is Babylon, js built-in a surface reflection method, which can be used to generate the surface reflection and wave effect, on the flat terrain effect is good, but there are bugs in the rugged terrain, see this description: forum.babylonjs.com/t/questions…

2. Deal with the distortion of ground texture:

In the aforementioned blog post, the slope block has texture distortion:

You can see that the grass on the slope is bigger than the grass on the platform

This is because the texture coordinates of the triangles that make up the slope and the platform have the same size but different areas, which is “not flat” in the technical term. The official solution of Babylon. Js is to change the texture to “flat” through two steps of “expanding vertices” and “calculating UV” after the terrain changes. Alternatively, just retain the vertical-position calculation function of Babybabylon. Js and use self-calculated UV coordinates instead of the automatically generated babybabylon. And my decision is refer to Babylon, js “MeshBuilder. CreateRibbon” way to write your own “FrameGround. MyCreateRibbon2” method to solve this problem, FrameGround. MyCreateRibbon2 method in FrameGround2. Js file. The official solution here: forum.babylonjs.com/t/which-way…

3, terrain is set to end, execute FrameGround. ExportObjGround method, the map export ObjGround20210427. Babylon, ‘for model file.

Set up scene and navigation grid

Babylon. Use js wasm Recast pathfinding engine version to group pathfinding, can be in here to see the official document, https://doc.babylonjs.com/extensions/crowdNavigation, Chinese and English versions of https://www.cnblogs.com/ljzc002/p/14831648.html in the here to see (from word copy to blog garden lost the color code, later in making the upload word version)

Personal understanding of the “navigation grid” is to combine the “reachable parts” of the multiple grids that make up the terrain of the scene into one grid, and then calculate the position relationship between the units and the navigation grid to determine how the units move to the target location.

Testslopnav3.html is the entry file for the navigator, and this is just the part that wasn’t mentioned in the previous blog

1. Program entry

1 function webGLStart() 2 { 3 initScene(); InitArena (); // Initialize the camera and light, note that the camera initialization includes the preparation of the drag frame. //obj_ground={}; 6 InitMouse(); 7 window.addeventListener ("resize", function () {if (engine) {9 engine.resize(); 10 var width=canvas.width; 11 var height=canvas.height; 12 var fov=camera0.fov; Camera0.pos_kuangbase = New BABYLON.Vector3(-camera0.dis*Math. Tan (fov) 14, camera0.dis*Math.tan(fov)*height/width, camera0.dis); 15 } 16 },false); 17 18 FrameGround. / / import just edit maps ImportObjGround (".. /.. /ASSETS/SCENE/","ObjGround20210427.babylon",webGLStart2,obj_ground,false); 19 20 21}Copy the code

Originally, the plan was to calculate the position of the selection box by reading the camera’s FOV property (denoted by half of the radian of the camera’s horizontal view Angle; the default starting value of Surible.js is 0.8), but in practice it was found that Surible.js would automatically modify the camera’s view Angle size when the screen scale changed. This automatic modification does not change the foV properties of the camera! So forgo this approach and look at testSlopnav2.html to see the code that uses FOV to calculate the location of the selection box.

InitScene ();

1 function initScene() 2 { 3 navigationPlugin = new BABYLON.RecastJSPlugin(); 4 var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 14, -14), scene); 5 //camera.setTarget(BABYLON.Vector3.Zero()); 6 camera.rotation.x=Math.PI/4; AttachControl (canvas, true); MyGame. Camera0 =camera; 9 camera0=camera; 10 camera.move=0; Length0 =19.8; / / 14 * math.h pow,0.5 (2); // The camera is looking down from (0, 14, -14) at a 45 degree Angle, The camera to a distance of 19.8 sea level 12 camera. Dis = 3 / / to box marquee 13 the distance from the camera. The camera path_line_kuang = [new BABYLON. Vector3 (0, 14, -14),new BABYLON.Vector3(0, -14, -14)]; 14 camera. / / the path of the wireframe line_kuang = BABYLON. MeshBuilder. CreateDashedLines (15 "line_kuang" / / wireframe object, {points: camera.path_line_kuang, updatable: true}, scene); // There should be no instance!! 16 camera.line_kuang.renderingGroupid =3; 17 camera.line_kuang.parent=camera; 18 camera.line_kuang.isVisible=true; // Every time you create a dashed line through instance, you inherit it. 19 camera.mesh_kuang=new BABYLON.Mesh("mesh_kuang"); 20 21 camera0. Mesh_kuang0 = new BABYLON. MeshBuilder. CreatePlane (" mesh_kuang0 "/ / a with ground such as invisible grid, used to receive mouse events, 22 {width: 100, height: 100, sideOrientation: BABYLON.Mesh.DOUBLESIDE}, scene); 23 camera0.mesh_kuang0.parent = camera0; 24 camera0.mesh_kuang0.renderiGroupId = 0; Camera0.mesh_kuang0.position. z=3 26 CamerA0.mesh_kuang0.rotation. X = math.pi; 27 28 var light0 = new BABYLON.HemisphericLight("light0", new BABYLON.Vector3(0, 1, 0), scene); 29 light0. Diffuse = new BABYLON. Color5 (1,1,1); Specular = new BABYLON.Color3(0,0,0); // this "color" is from top down, bottom receives 100%, side receives 50%, top does not have 30 light0. GroundColor = new BABYLON. Color5 (1,1,1); HemisphericLight = new Backbabylon. Light("light1", new Backbabylon.Vector3(0, 1, 0), scene); 33. / / light intensity = 0.7; 34}Copy the code

This creates a “mask grid” large enough to receive mouse-drag events

3. Mouse and keyboard control methods will be introduced later

4. After loading the model, execute the webGLStart2 method to establish the navigation grid (refer to the official document for the code here)

1 function webGLStart2() 2 { 3 arr_ground=[obj_ground["ground1"].ground_base]; Var navmeshParameters = {5 cs: 0.2, 6 ch: 0.2, 7 walkableSlopeAngle: 90, 8 walkableHeight: 1.0, 9 walkableClimb: 1, 10 walkableRadius: 1, 11 maxEdgeLen: 12., 12 maxSimplificationError: 1.3, 13 minRegionArea: 8, 14 mergeRegionArea: 20, 15 maxVertsPerPoly: 6, 16 detailSampleDist: 6, 17 detailSampleMaxError: 1, 18 }; 19 navigationPlugin.createNavMesh(arr_ground, navmeshParameters); / / set up navigation grid 20 / / var navmeshdebug = navigationPlugin createDebugNavMesh (scene); // Navmeshdebug. position = new BABYLON.Vector3(0, 0.01, 0); 22 // navmeshdebug.renderingGroupId=3; 23 // navmeshdebug.myname="navmeshdebug"; 24 // var matdebug = new BABYLON.StandardMaterial('matdebug', scene); 5 // matdebug.diffuseColor = new BABYLON.Color3(0.1, 0.2, 1); 26 // matdebug.alpha = 0.2; 27 // navmeshdebug.material = matdebug; 28, 29 30 var / / crowd crowd. = navigationPlugin createCrowd (40, 0.1, scene); // Create a group with a maximum of 40 31 var I; 32 var agentParams = {radius: 0.1, 34 height: 0.2, 35 maxAcceleration: 4.0, 36 maxSpeed: 1.0, 37 collisionQueryRange: 0.5, 38 pathOptimizationRange: 0.0, 39 separationWeight: 1.0}; 40 41 for (i = 0; i <20; I++) {// create 20 units on the right side of the channel. 43 var id="a_"+i; 44 var agentCube = BABYLON.MeshBuilder.CreateBox("cube_"+id, { size: width, height: width*2 }, scene); 45 / / var targetCube = BABYLON. MeshBuilder. CreateBox (" cube ", {size: 0.1, height: 0.1}, scene). 46 agentCube.renderingGroupId=3; 47 //targetCube.renderingGroupId=3; 48 //var matAgent = new BABYLON.StandardMaterial('mat2_'+id, scene); 49 //var variation = Math.random(); 50 // matagent. diffuseColor = new BABYLON.Color3(0.4 + variation * 0.6, 0.3, 1.0 - variation * 0.3); 51 //targetCube.material = matAgent; 52 agentCube.material = MyGame.materials.mat_sand; 53 var randomPos = navigationPlugin. GetRandomPointAround (new BABYLON. Vector3 (20.0, 0.2, 0), 0.5); 54 var transform = new BABYLON.TransformNode(); 55 //agentCube.parent = transform; 56 var agentIndex = crowd.addAgent(randomPos, agentParams, transform); 57 //transform.pathPoints=[transform.position]; 60 waiting :"waiting", 61 doing:"standing", 62 being:"none", 63} 64 var unit={idx:agentIndex, TRF: Transform, Mesh :agentCube, Target: New BABYLON.Vector3(20.0, 2.1, 0) 65 ,data:{state:state,id:id}}; 66 agentCube.unit=unit 67 arr_unit.push(unit); 68} 69 for (I = 0; i <20; I++) {70 var width = 0.20; 71 var id="b_"+i; 72 var agentCube = BABYLON.MeshBuilder.CreateBox("cube_"+id, { size: width, height: width*2 }, scene); 73 / / var targetCube = BABYLON. MeshBuilder. CreateBox (" cube ", {size: 0.1, height: 0.1}, scene). 74 agentCube.renderingGroupId=3; 75 //targetCube.renderingGroupId=3; 76 //var matAgent = new BABYLON.StandardMaterial('mat2_'+id, scene); 77 //var variation = Math.random(); DiffuseColor = new BABYLON. Color5 (0.4 + variation * 0.6, 0.3, 1.0 - variation * 0.3); 79 //targetCube.material = matAgent; 80 agentCube.material = MyGame.materials.mat_sand; 81 var randomPos = navigationPlugin. GetRandomPointAround (new BABYLON. Vector3 (20.0, 0.2, 0), 0.5); 82 var transform = new BABYLON.TransformNode(); 83 //agentCube.parent = transform; 84 var agentIndex = crowd.addAgent(randomPos, agentParams, transform); 85 //transform.pathPoints=[transform.position]; 86 var state={ 87 feeling:"free", 88 wanting:"waiting", 89 doing:"standing", 90 being:"none", 91} 92 var unit={IDx :agentIndex, TRF: Transform, Mesh :agentCube, Target: New BABYLON.Vector3(20.0, 2.1, 0) 93 ,data:{state:state,id:id}}; 94 agentCube.unit=unit; 95 arr_unit.push(unit); 96 } 97 var startingPoint; 98 var currentMesh; 99 var pathLine; 100, 101,Copy the code

5, listen to the right mouse click:

1 var startingPoint; 2 var currentMesh; 3 var pathLine; 4 5 document. onContextMenu = function(evT){// onContextMenu = function(evT); 8 return false; 9 function onContextMenu(evT) 11 {12 var pickInfo = scene.pick(scene.pointerx, scene.pointery, (mesh)=>(mesh.id! ="mesh_kuang0"), false, MyGame.camera0); 13 If (pickinfo.hit)// Normally, right clicking will point to the mask grid we created earlier (mesh_kuang0), but due to the filter parameter Settings in the previous line of code, 14 {15 var mesh = pickinfo.pickedmesh; 16 // If (mesh.myname=="navmeshdebug")// This is restricted to clicking on the navigation grid. 18 var agents = crowd.getagents (); 19 var len=arr_selected.length; // For each selected unit (shown in white) 20 var I; 21 for (i=0; i<len; Var unit=arr_selected[I]; var unit=arr_selected[I]; 23 var agent=agents[unit.idx]; 24 unit.data.state.doing="walking"; / / modify the state of the unit 25 crowd. AgentGoto (agent, navigationPlugin getClosestPoint (startingPoint)); 26 // End pathfinding with agentTeleport method? 27 var pathPoints=navigationPlugin.computePath(crowd.getAgentPosition(agent), navigationPlugin.getClosestPoint(startingPoint)); 28 unit.lastPoint=pathPoints[0]; Unshift (ununit.trf.position); // Retain the previous node to determine whether to reduce the number of nodes in the pathline. PathPoints =pathPoints; // Set the first point of the path to the moving object itself. / / save the expected route of 31 / / depending on the tour is expected to draw a dotted line 32 unit.and pathLine = BABYLON. MeshBuilder. CreateDashedLines (" ribbon_ "+ unit. Independence idx, {points: unit.pathPoints, updatable: true, instance: unit.pathLine}, scene); 33 unit.pathLine.renderingGroupId=3; 34 } 35 //var pathPoints = navigationPlugin.computePath(crowd.getAgentPosition(agents[0]), navigationPlugin.getClosestPoint(startingPoint)); 36 //pathLine = BABYLON.MeshBuilder.CreateDashedLines("ribbon", {points: pathPoints, updatable: true, instance: pathLine}, scene); 37} 38 39}Copy the code

6. Adjust the grid and dotted lines that represent units before rendering each frame

1 scene.onBeforeRenderObservable.add(()=> { 2 var len = arr_unit.length; 3 //var flag_rest=false; // Each movement unit should have its own end-of-movement symbol !!!! 4 for(let i = 0; i<len; I ++)// For each unit in the scene 5 {6 var ag = arr_unit[I]; 7 ag.mesh. Position = crowd. GetAgentPosition (ag.idx); If (ag.data.state.doing=="walking") 9 {10 11 let vel = crowd. GetAgentVelocity (ag.idx); / / the movement speed of 12 crowd. GetAgentNextTargetPathToRef (ag) independence idx, ag. Target); Target 13 if (vel.length() > 0.2)// There is a slow acceleration phase when the movement starts. 14 { 15 16 vel.normalize(); 17 var desiredRotation = Math.atan2(vel.x, vel.z); Y = ag.mesh.rotation. Y + (desiredRotation - ag.mesh.rotation. Y) * 0.05; 19 var pos=ag.target; 20 var posl= ag.lastpoint; 21 ag.pathPoints[0]=ag.mesh. Position; 22 //console.log(ag.pathPoints[0],pos); // Update the dotted line with the instance attribute! 23 ag.pathLine = BABYLON.MeshBuilder.CreateDashedLines("ribbon_"+ag.idx 24 , {points: ag.pathPoints, updatable: true, instance: ag.pathLine}, scene); 25 if(pos&&posl) 26 { 27 if(pos.x! =posl.x||pos.y! =posl.y||pos.z! =posl.z)// If the next navigation point changes 28 {29 //console.log(pos,posl); 30 ag. PathPoints. Splice (1, 1); // The vertex of the dotted line is reduced by 31 ag.lastPoint=ag.pathPoints[1]; Ag.target. position= ag.lastpoint; 33 //console.log(ag.pathPoints.length); 34 // ag.pathLine = BABYLON.MeshBuilder.CreateDashedLines("ribbon_"+ag.idx 35 // , {points: ag.pathPoints, updatable: true, instance: ag.pathLine}, scene); 36 37 } 38 } 39 else 40 { 41 //console.log(ag); 42 43} 44} 45 else {// If in a unit of time (1s?) Ag.target =ag.mesh. Position; 47 if (vel.length() < 0.01&&ag.pathpoint.length ==2) 48 {49 crowd.agentteleport (ag.idx, ag.mesh. Position); 50 // If the speed is too slow, teleport the unit to its current location to stop pathfinding (there is no manual way to stop pathfinding in the documentation) - "What if I get stuck in traffic? 51 AG.data.state. doing=="standing" 52 console.log(" unit "+ag.mesh. Id +" Stop navigation ") 53} 54 55} 56} 57 58} 59 });Copy the code

Thus we have completed the preparation of navigation

4. RTS keyboard and mouse control

The controlRts3. js file contains the following contents:

  1 //用于RTS控制的相机-》用大遮罩多层pick代替计算框选位置
  2 var node_temp;
  3 function InitMouse()//初始化事件监听
  4 {
  5     canvas.addEventListener("blur",function(evt){//监听失去焦点
  6         releaseKeyStateOut();
  7     })
  8     canvas.addEventListener("focus",function(evt){//改为监听获得焦点,因为调试失去焦点时事件的先后顺序不好说
  9         releaseKeyStateIn();
 10     })
 11 
 12     //scene.onPointerPick=onMouseClick;//如果不attachControl onPointerPick不会被触发,并且onPointerPick必须pick到mesh上才会被触发
 13     canvas.addEventListener("click", function(evt) {//这个监听也会在点击GUI按钮时触发!!
 14         onMouseClick(evt);//
 15     }, false);
 16     canvas.addEventListener("dblclick", function(evt) {//是否要用到鼠标双击??
 17         onMouseDblClick(evt);//
 18     }, false);
 19     scene.onPointerMove=onMouseMove;
 20     scene.onPointerDown=onMouseDown;
 21     scene.onPointerUp=onMouseUp;
 22     //scene.onKeyDown=onKeyDown;
 23     //scene.onKeyUp=onKeyUp;
 24     window.addEventListener("keydown", onKeyDown, false);//按键按下
 25     window.addEventListener("keyup", onKeyUp, false);//按键抬起
 26     window.onmousewheel=onMouseWheel;//鼠标滚轮滚动
 27     node_temp=new BABYLON.TransformNode("node_temp",scene);//用来提取相机的姿态矩阵(不包括位置的姿态)
 28     node_temp.rotation=camera0.rotation;
 29 
 30     pso_stack=camera0.position.clone();//用来在切换控制方式时保存相机位置
 31 }
 32 function onMouseDblClick(evt)//这段没用
 33 {
 34     var pickInfo = scene.pick(scene.pointerX, scene.pointerY, null, false, camera0);
 35     if(pickInfo.hit)
 36     {
 37         var mesh = pickInfo.pickedMesh;
 38         if(mesh.name.split("_")[0]=="mp4")//重放视频
 39         {
 40             if(obj_videos[mesh.name])
 41             {
 42                 var videoTexture=obj_videos[mesh.name];
 43 
 44                     videoTexture.video.currentTime =0;
 45 
 46             }
 47         }
 48     }
 49 }
 50 function onMouseClick(evt)//鼠标单击
 51 {
 52     if(flag_view=="locked") {
 53         ThrowSomeBall();//没用
 54     }
 55     if(flag_view=="rts"&&evt.button!=2) {//选择了单个单位《-目前是rts控制状态,并且不是右键单击
 56         evt.preventDefault();
 57         var pickInfo = scene.pick(scene.pointerX, scene.pointerY, (mesh)=>(mesh.id.substr(0,5)=="cube_")
 58             , false, camera0);
 59         if(pickInfo.hit)
 60         {
 61             var mesh = pickInfo.pickedMesh;
 62             resetSelected();
 63             mesh.material=MyGame.materials.mat_frame;//改变被选中的单位的显示
 64             arr_selected.push(mesh.unit);//将被选中的单位放到“被选中数组”中
 65         }else
 66         {
 67             resetSelected();
 68         }
 69     }
 70 }
 71 var lastPointerX,lastPointerY;
 72 var flag_view="free"
 73 var obj_keystate=[];
 74 var pso_stack;
 75 var flag_moved=false;//在拖拽模式下有没有移动,如果没移动则等同于click
 76 var point0,point;//拖拽时点下的第一个点与当前移动到的点
 77 function onMouseMove(evt)//鼠标移动响应
 78 {
 79 
 80     if(flag_view=="rts")
 81     {
 82         evt.preventDefault();
 83         if(camera0.line_kuang.isVisible)
 84         {
 85             flag_moved=true;
 86             drawKuang();//画框
 87         }
 88     }
 89     lastPointerX=scene.pointerX;
 90     lastPointerY=scene.pointerY;
 91 }
 92 function drawKuang(){
 93     var m_cam=camera0.getWorldMatrix();
 94     if(!point0)
 95     {//第一次按下鼠标时在蒙板网格上点到的点
 96         var pickInfo0 = scene.pick(downPointerX, downPointerY, (mesh)=>(mesh.id=="mesh_kuang0")
 97             , false, camera0);
 98         if(pickInfo0.hit)
 99         {
100             point0 = pickInfo0.pickedPoint;
101             point0=BABYLON.Vector3.TransformCoordinates(point0,m_cam.clone().invert());//转为相机的局部坐标系中的坐标
102         }
103     }
104     if(point0)
105     {
106         var pickInfo = scene.pick(scene.pointerX, scene.pointerY, (mesh)=>(mesh.id=="mesh_kuang0")
107             , false, camera0);
108         if(pickInfo.hit)
109         {//当前鼠标在蒙板网格上点到的点,根据这两个点绘制一个线框
110             point = pickInfo.pickedPoint ;
111             point=BABYLON.Vector3.TransformCoordinates(point,m_cam.clone().invert());
112             camera0.path_line_kuang=[point0,new BABYLON.Vector3(point.x, point0.y, 3)
113                 ,point,new BABYLON.Vector3(point0.x, point.y, 3),point0];//封口
114             camera0.line_kuang= BABYLON.MeshBuilder.CreateDashedLines("line_kuang"
115                 , {points: camera0.path_line_kuang, updatable: true, instance: camera0.line_kuang}, scene);
116         }
117     }
118 }
119 var downPointerX,downPointerY;
120 function onMouseDown(evt)//鼠标按下响应
121 {
122     if(flag_view=="rts"&&evt.button!=2) {
123         evt.preventDefault();
124         //单选单位的情况放在click中
125         //显示框选框(四条线段围成的矩形)
126         downPointerX=scene.pointerX;
127         downPointerY=scene.pointerY;
128         camera0.line_kuang.isVisible=true;//将线框设为可见
129         drawKuang();
130     }
131 }
132 function onMouseUp(evt)//鼠标抬起响应
133 {
134     if(flag_view=="rts"&&evt.button!=2) {
135         evt.preventDefault();
136         if(camera0.line_kuang.isVisible)
137         {
138             camera0.line_kuang.isVisible=false;//令线框不可见
139             if(flag_moved)
140             {
141                 flag_moved = false;
142           //依靠point0和point,在之前画线框的位置建立一个不可见的平面网格,把它叫做“框网格”
143                 var pos = new BABYLON.Vector3((point0.x + point.x) / 2, (point0.y + point.y) / 2, 3);
144                 var width2 = Math.abs(point0.x - point.x);
145                 var height2 = Math.abs(point0.y - point.y);
146                 if (camera0.mesh_kuang) {
147                     camera0.mesh_kuang.dispose();
148                 }
149                 camera0.mesh_kuang = new BABYLON.MeshBuilder.CreatePlane("mesh_kuang"
150                     , {width: width2, height: height2, sideOrientation: BABYLON.Mesh.DOUBLESIDE}, scene);
151                 camera0.mesh_kuang.parent = camera0;
152                 camera0.mesh_kuang.renderingGroupId = 0;//测试时可见,实际使用时不可见
153                 camera0.mesh_kuang.position = pos;
154                 camera0.mesh_kuang.rotation.x = Math.PI;
155                 //camera0.mesh_kuang.material=MyGame.materials.mat_sand;
156 
157                 //发射射线
158                 resetSelected();//清空当前选中的单位
159                 requestAnimFrame(function(){//这里要延迟到下一帧发射射线,否则框网格还没绘制,射线射不到它
160                     arr_unit.forEach((obj, i) => {//从相机到每个可控单位发射射线
161                         var ray = BABYLON.Ray.CreateNewFromTo(camera0.position, obj.mesh.position);
162                         //console.log(i);
163                         //var pickInfo = scene.pickWithRay(ray, (mesh)=>(mesh.id=="mesh_kuang0"));
164                         var pickInfo = ray.intersectsMesh(camera0.mesh_kuang);//难道是因为网格尚未渲染,所以找不到?
165                         if (pickInfo.hit)//如果相机与物体的连线穿过了框网格,则这个物体应该被选中!
166                         {
167                             obj.mesh.material = MyGame.materials.mat_frame;
168                             arr_selected.push(obj);
169                         }
170                         //ray.dispose();//射线没有这个方法?
171                     })
172                     camera0.mesh_kuang.dispose();//用完后释放掉框网格
173                     camera0.mesh_kuang = null;
174                 })
175 
176             }
177         }
178         point0=null;
179         point=null;
180 
181     }
182 }
183 function onKeyDown(event)//按下按键
184 {
185     if(flag_view=="rts") {
186         event.preventDefault();
187         var key = event.key;
188         obj_keystate[key] = 1;//修改按键状态,
189         if(obj_keystate["Shift"]==1)//注意,按下Shift+w时,event.key的值为W!
190         {
191             obj_keystate[key.toLowerCase()] = 1;
192         }
193     }
194     else {
195         var key = event.key;
196         if(key=='f')
197         {
198             if(DoAni)
199             {
200                 DoAni();
201             }
202         }
203     }
204 }
205 function onKeyUp(event)//键盘按键抬起
206 {
207     var key = event.key;
208     if(key=="v"||key=="Escape")
209     {
210         event.preventDefault();
211         if(flag_view=="rts")//切换为rts控制
212         {
213             flag_view="free";
214             camera0.attachControl(canvas, true);
215             pso_stack=camera0.positions;
216 
217         }
218         else if(flag_view=="free")//切换为自由控制
219         {
220             flag_view="rts";
221             camera0.position= pso_stack;
222             resetCameraRotation(camera0);
223             camera0.detachControl()
224         }
225     }
226     if(flag_view=="rts") {
227         event.preventDefault();
228 
229         obj_keystate[key] = 0;
230         //因为shift+w=W,所以为了避免结束高速运动后,物体仍普速运动
231         obj_keystate[key.toLowerCase()] = 0;
232     }
233 }
234 function onMouseWheel(event){//鼠标滚轮转动响应
235     var delta =event.wheelDelta/120;
236     if(flag_view=="rts")
237     {
238         camera0.move+=delta;
239         if(camera0.move>16.8)//防止相机过于向下
240         {
241             delta=delta-(camera0.move-16.8);//沿着相机指向的方向移动相机
242             camera0.move=16.8;
243         }
244         //camera0.movePOV(0,0,delta);//轴向移动相机?<-mesh有这一方法,但camera没有!!《-所以自己写一个
245         movePOV(node_temp,camera0,new BABYLON.Vector3(0,0,delta));//camera0只能取姿态,不能取位置!!!!
246     }
247 }
248 function movePOV(node,node2,vector3)//将局部坐标系的移动转为全局坐标系的移动,参数:含有姿态矩阵的变换节点、要变换位置的对象、在物体局部坐标系中的移动
249 {
250     var m_view=node.getWorldMatrix();
251     v_delta=BABYLON.Vector3.TransformCoordinates(vector3,m_view);
252     var pos_temp=node2.position.add(v_delta);
253     node2.position=pos_temp;
254 }
255 function resetSelected(){
256     arr_selected.forEach((obj,i)=>{
257         //如果单位选中前后有外观变化,则在这里切换
258         obj.mesh.material=MyGame.materials.mat_sand;
259     });
260     arr_selected=[];
261 }
262 function resetCameraRotation(camera)//重置相机位置
263 {
264     //camera.movePOV(0,0,-camera0.move||0);//轴向移动相机?<-不需要,把转为自由相机前的位置入栈即可
265     //camera.move=0;
266     camera.rotation.x=Math.PI/4;
267     camera.rotation.y=0;
268     camera.rotation.z=0;
269 }
270 function releaseKeyStateIn(evt)
271 {
272     for(var key in obj_keystate)
273     {
274         obj_keystate[key]=0;
275     }
276     lastPointerX=scene.pointerX;
277     lastPointerY=scene.pointerY;
278 
279 }
280 function releaseKeyStateOut(evt)
281 {
282     for(var key in obj_keystate)
283     {
284         obj_keystate[key]=0;
285     }
286     // scene.onPointerMove=null;
287     // scene.onPointerDown=null;
288     // scene.onPointerUp=null;
289     // scene.onKeyDown=null;
290     // scene.onKeyUp=null;
291 }
292 
293 var pos_last;
294 var delta;
295 var v_delta;
296 function MyBeforeRender()
297 {
298     pos_last=camera0.position.clone();
299     scene.registerBeforeRender(
300         function(){
301             //Think();
302 
303         }
304     )
305     scene.registerAfterRender(
306         function() {
307             if(flag_view=="rts")
308             {//rts状态下,相机的位置变化
309                 var flag_speed=2;
310                 //var m_view=camera0.getViewMatrix();
311                 //var m_view=camera0.getProjectionMatrix();
312                 //var m_view=node_temp.getWorldMatrix();
313                 //只检测其运行方向?-》相对论问题!《-先假设直接外围环境不移动
314                 if(obj_keystate["Shift"]==1)//Shift+w的event.key不是Shift和w,而是W!!!!
315                 {
316                     flag_speed=10;
317                 }
318                 delta=engine.getDeltaTime();
319                 //console.log(delta);
320                 flag_speed=flag_speed*engine.getDeltaTime()/10;
321                 var r_cameramove=(camera0.length0-camera0.move)/camera0.length0//相机移动造成的速度变化
322                 if(r_cameramove<0.1)
323                 {
324                     r_cameramove=0.1;
325                 }
326                 if(r_cameramove>5)
327                 {
328                     r_cameramove=5;
329                 }
330                 flag_speed=flag_speed*r_cameramove;
331                 var v_temp=new BABYLON.Vector3(0,0,0);
332                 if(obj_keystate["w"]==1)
333                 {
334                     v_temp.z+=0.1*flag_speed;
335 
336                 }
337                 if(obj_keystate["s"]==1)
338                 {
339                     v_temp.z-=0.1*flag_speed;
340                 }
341                 if(obj_keystate["d"]==1)
342                 {
343                     v_temp.x+=0.1*flag_speed;
344                 }
345                 if(obj_keystate["a"]==1)
346                 {
347                     v_temp.x-=0.1*flag_speed;
348                 }
349                 // if(obj_keystate[" "]==1)
350                 // {
351                 //     v_temp.y+=0.05*flag_speed;
352                 // }
353                 // if(obj_keystate["c"]==1)
354                 // {
355                 //     v_temp.y-=0.05*flag_speed;
356                 // }
357 
358                 //camera0.position=camera0.position.add(BABYLON.Vector3.TransformCoordinates(v_temp,camera0.getWorldMatrix()).subtract(camera0.position));
359                 //engine.getDeltaTime()
360                 //v_delta=BABYLON.Vector3.TransformCoordinates(v_temp,m_view);
361                 var pos_temp=camera0.position.add(v_temp);
362                 camera0.position=pos_temp;
363                 // if(camera0.line_kuang.isVisible)
364                 // {
365                 //     camera0.line_kuang= BABYLON.MeshBuilder.CreateDashedLines("line_kuang"
366                 //         , {points: camera0.path_line_kuang, updatable: true, instance: camera0.line_kuang}, scene);
367                 // }
368             }
369             pos_last=camera0.position.clone();
370         }
371     )
372     engine.runRenderLoop(function () {
373         engine.hideLoadingUI();
374         if (divFps) {
375             divFps.innerHTML = engine.getFps().toFixed() + " fps";
376         }
377         scene.render();
378     });
379 }
380 function sort_compare(a,b)
381 {
382     return a.distance-b.distance;
383 }
384 var requestAnimFrame = (function() {//下一帧,复制自谷歌公司开源代码
385     return window.requestAnimationFrame ||
386         window.webkitRequestAnimationFrame ||
387         window.mozRequestAnimationFrame ||
388         window.oRequestAnimationFrame ||
389         window.msRequestAnimationFrame ||
390         function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {
391             window.setTimeout(callback, 1000/60);
392         };
393 })();
Copy the code

This completes a basic RTS control effect.

V. Next step

Move the cursor over different objects to display different animations, and add AI threads to add AI calculations for each unit.