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.