Erlo

在WebGL场景中管理多个卡牌对象的实验

2018-09-22 22:37:52 发布   921 浏览  
页面报错/反馈
收藏 点赞

  这篇文章讨论如何在基于Babylon.js的WebGL场景中,实现多个简单卡牌类对象的显示、选择、分组、排序,同时建立一套实用的3D场景代码框架。由于作者美工能力有限,所以示例场景视觉效果可能欠佳,本文的重点在于对相关技术的探讨。

  因为文章比较长,读者可以考虑将网页导出为mhtml格式,使用Word浏览。Chrome浏览器导出mhtml文件的方法见末尾。

一、显示效果:

1、访问https://ljzc002.github.io/CardSimulate/HTML/TEST2.html查看“卡牌模拟页面”:

  场景中间是三个作为参照物的小球,视口平面的中间是一个用Babylon.js GUI制作的准星,默认鼠标与准星锁定在一起,直接移动鼠标即可改变相机视角,使用WASD Shift 空格键可以控制相机前、左、后、右、下、上运动(可能将Ctrl键设为向下更符合传统,但是没有找到禁用浏览器Ctrl+s快捷键的方法,只好用Shift代替)。因为光标被锁定,将这种浏览状态命名为“first_lock”。

2、按下Alt键,75张卡片通过动画移入相机视野,同时相机的位置被固定(但仍可以通过拖动鼠标改变视角):

  点击右侧的“向上两行”和“向下两行”按钮可以上下滚动卡片,再次按下Alt键将隐藏卡片,同时恢复相机的移动和光标的锁定。因为这种浏览状态主要用来点选场景中的物体,将它命名为“first_pick”。

3、鼠标左键单击一张卡片,卡片将处于“选中状态”(绿色边缘),再次左键单击处于选中状态的卡片,卡片将被放大拉近显示,再左键单击将恢复原位:

  执行动画时会禁用用户的控制,完全由动画控制视角,所以将这种浏览状态命名为“first_ani”。

4、模仿Windows的文件多选编写了卡片多选功能,按下Ctrl时可以点选多个卡片,按下Shift时可以选取首尾之间的所有卡片:

5、选中若干张卡片后,按1-5键可以将被选中的卡片编为1-5队,被编队的卡片将按编队顺序显示在最高处,同时编队的前面会显示队号标记:

6、在first_pick状态可以使用上下左右方向键进行场景漫游,可以看到场景中的所有对象:

 二、代码实现:

1、文件结构:

CardSimulate工程的文件结构如下图所示:

其中LIB目录下是从网上下载的代码库

  babylon.32.all.maxs.js是Babylon.js引擎库

  earcut.dev.js是一个Babylon.js扩展,其功能是在网格上挖洞

  stat.js是用来显示帧数的代码

MYLIB是自己编写的代码库

  Events.js是一些用来处理事件的方法

  FileText.js是与文件处理相关的代码

  newland.js是自己编写的一些Babylon.js辅助类

  View.js是html视图的一些相关方法

PAGE是直接操纵这个页面(WebGL场景)的代码库

  Character.js是场景中出现的各种对象的类(比如卡牌网格、相机网格)

  Control20180312.js是用来处理鼠标键盘输入的代码

  DrawCard.js是用来绘制卡牌的代码

  FullUI.js是用来绘制全局(全屏)UI的代码

  Game.js是游戏类,存储用来调度整个场景的信息

  HandleCard.js是用来处理已经绘制出的卡牌的代码,后期考虑和DrawCard.js整合在一起

  HandleCard2.js是一个分枝修改版

  Moves.js是运动计算代码

  tab_carddata.js里是卡牌种类信息

  tab_somedata.js里是其他辅助信息

2、代码入口与场景初始化:

  A、代码由TEST2.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     link href="../CSS/stat.css" rel="stylesheet">
 8     script src="../JS/LIB/babylon.32.all.maxs.js">script>
 9     script src="../JS/LIB/stat.js">script>
10     script src="../JS/MYLIB/Events.js">script>
11     script src="../JS/MYLIB/FileText.js">script>
12     script src="../JS/MYLIB/newland.js">script>
13     script src="../JS/MYLIB/View.js">script>
14     script src="../JS/PAGE/Game.js">script>
15     script src="../JS/PAGE/Character.js">script>
16     script src="../JS/PAGE/Control20180312.js">script>
17     script src="../JS/PAGE/Moves.js">script>
18     script src="../JS/PAGE/DrawCard.js">script>
19     script src="../JS/PAGE/tab_carddata.js">script>
20     script src="../JS/PAGE/tab_somedata.js">script>
21     script src="../JS/PAGE/HandleCard2.js">script>
22     script src="../JS/PAGE/FullUI.js">script>
23 head>
24 body>
25 div id="div_allbase">
26     canvas id="renderCanvas">canvas>
27     div id="fps" style="z-index: 301;">div>
28 div>
29 body>
30 script>
31     var VERSION=1.0,AUTHOR="lz_newland@163.com";
32     var machine,canvas,engine,scene,gl,MyGame={};
33     canvas = document.getElementById("renderCanvas");
34     engine = new BABYLON.Engine(canvas, true);
35     engine.displayLoadingUI();
36     gl=engine._gl;//决定在这里结合使用原生OpenGL和Babylon.js;
37     scene = new BABYLON.Scene(engine);
38     var divFps = document.getElementById("fps");
39 
40     var MyGame={};
41     window.onload=beforewebGL;
42     function beforewebGL()
43     {
44         if(engine._webGLVersion==2.0)//输出ES版本
45         {
46             console.log("ES3.0");
47         }
48         else{
49             console.log("ES2.0");
50         }
51         MyGame=new Game(0,"first_pick","","http://127.0.0.1:8082/");//建立MyGame对象用来进行全局调度
52         /*0-startWebGL
53          * */
54         webGLStart();
55     }
。。。
56 script> 57 html>

  但与前面的简单场景将主要代码都写在webGLStart方法中不同,对于较为复杂的流程最好将流程的每个阶段写在单独的方法里,对于较多的对象则最好提取对象的共同点作为一个“类”,将每个对象作为类的实例。这样可以将程序的复杂度分解,每次只关注其中的一小部分,降低编程难度。(设计模式的本质是对变量名进行管理,理论上讲,如果编程者的记忆力足够强、编程者之间的沟通效率足够高,则这些所谓的“设计模式”都可以省略)

  B、在webGLStart方法中对场景初始化流程进行了划分,各个流程如注释所示:

 1 //对象框架架构
 2     function webGLStart()
 3     {
 4         //initWebSocket();//如何确保上一环结成功才开启下一环节?
 5         initScene();//初始化场景,包括最初入门教程里的那些东西
 6         initArena();//初始化地形,包括天空盒,参照物等
 7         initEvent();//初始化事件
 8         initUI();//初始化场景UI
 9         initObj();//初始化一开始存在的可交互的物体
10         initLoop();//初始化渲染循环
11         MyGame.init_state=1;//更新初始化状态
12         engine.hideLoadingUI();//隐藏载入UI
13         //MyGame.flag_startr=1;//这个是通过nohurry计时器自动启动的,不需要手动启动
14     }

  C、初始化场景

 1 function initScene()
 2     {//光照
 3         var light0 = new BABYLON.HemisphericLight("light0", new BABYLON.Vector3(0, 1, 0), scene);
 4         light0.diffuse = new BABYLON.Color3(1,1,1);//这道“颜色”是从上向下的,底部收到100%,侧方收到50%,顶部没有
 5         light0.specular = new BABYLON.Color3(0,0,0);
 6         light0.groundColor = new BABYLON.Color3(1,1,1);//这个与第一道正相反
 7         MyGame.lights.light0=light0;//将光照变量交给MyGame对象管理
 8         mesh_arr_cards=new BABYLON.Mesh("mesh_arr_cards", scene);
 9         //相机对象
10         var camera0= new BABYLON.FreeCamera("FreeCamera", new BABYLON.Vector3(0, 0, 0), scene);
11         //camera0.layerMask = 2;
12         //camera0.position=new BABYLON.Vector3(0, 0, -20);
13         camera0.minZ=0.001;
14         scene.activeCameras.push(camera0);
15         
16         //用BallMan作为CameraMesh的mesh
17         var player = new BallMan();
18         var obj_p={};//初始化参数
19         //计划不使用物理引擎
20         var mesh_ballman=new BABYLON.Mesh("mesh_ballman",scene);
21         obj_p.mesh=mesh_ballman;
22         obj_p.name="本机";//显示的名字
23         obj_p.id="本机";//WebSocket Sessionid
24         obj_p.image="../ASSETS/IMAGE/Rainbow.jpg";
25         player.init(
26                 obj_p,scene
27         );
28 
29         var cameramesh=new CameraMesh();
30         var obj_p={};//初始化参数
31         obj_p.mesh=mesh_ballman;
32         obj_p.mesh.isVisible=false;
33         obj_p.mesh.position=new BABYLON.Vector3(0,0,-20);
34         if(obj_p.mesh.ballman)
35         {
36             obj_p.mesh.ballman.head.position=obj_p.mesh.position.clone();
37         }
38         obj_p.methodofmove="host20171018";
39         obj_p.name="FreeCamera";//显示的名字
40         obj_p.id="FreeCamera";//WebSocket Sessionid
41         obj_p.camera=camera0;
42         //obj_p.image="assets/image/play.png";
43         obj_p.flag_objfast=5;
44         cameramesh.init(
45                 obj_p,scene
46         );
47         MyGame.arr_myplayers[obj_p.name]=cameramesh;
48         MyGame.player=cameramesh;
49         MyGame.Cameras.camera0=camera0;
50         camera0.position=cameramesh.mesh.position.clone();
51         cameramesh.mesh.rotation=camera0.rotation.clone();
52         mesh_arr_cards.position=MyGame.player.mesh.ballman.backview._absolutePosition.clone();
53     }

  其中mesh_arr_cards是所有手牌的父网格,用来对手牌进行定位,事实上这个对象放在initArena或者initObj阶段更加合理,但是因为相机对象的一些事件和这个网格有关,只好放在场景初始化阶段。BallMan的外观是一个球体网格,用来代表场景中的玩家,其用法可以参考https://www.cnblogs.com/ljzc002/p/7274455.html;CameraMesh是一个网格与相机的结合体,在第三人称时用户将能看见自己操纵的单位(关于BallMan和CameraMesh类的参数将在后面详细介绍)。最后把各种对象都交给MyGame统一管理。

  D、初始化环境

 1 function initArena()
 2     {
 3         var mesh_base=new BABYLON.MeshBuilder.CreateSphere("mesh_base",{diameter:1},scene);
 4         mesh_base.material=MyGame.materials.mat_green;
 5         mesh_base.position.x=0;
 6         mesh_base.renderingGroupId=2;
 7         //mesh_base.layerMask=2;
 8         var mesh_base1=new BABYLON.MeshBuilder.CreateSphere("mesh_base1",{diameter:1},scene);
 9         mesh_base1.position.y=10;
10         mesh_base1.position.x=0;
11         mesh_base1.material=MyGame.materials.mat_green;
12         mesh_base1.renderingGroupId=2;
13         //mesh_base1.layerMask=2;
14         var mesh_base2=new BABYLON.MeshBuilder.CreateSphere("mesh_base2",{diameter:1},scene);
15         mesh_base2.position.y=-10;
16         mesh_base2.position.x=0;
17         mesh_base2.material=MyGame.materials.mat_green;
18         mesh_base2.renderingGroupId=2;
19         //mesh_base2.layerMask=2;
20         for(var i=0;i//建立五个标示组号的标记网格,标记从一(而不是零)开始
21         {
22             var plane=new BABYLON.MeshBuilder.CreatePlane("mesh_groupicon"+(i+1),{size:5},scene);
23             var mat_plane = new BABYLON.StandardMaterial("mat_plane"+(i+1), scene);
24             var texture_plane= new BABYLON.DynamicTexture("texture_plane"+(i+1), {width:100, height:100}, scene);
25             mat_plane.diffuseTexture =texture_plane;
26             plane.material=mat_plane;
27             var font = "bold 60px monospace";
28             texture_plane.drawText((i+1), 40, 70, font, "white", "green", true, true);//第一个是文字颜色,第二个则是完全填充的背景色
29             plane.position.x=-16;
30             plane.position.z=-2;
31             plane.renderingGroupId=2;
32             //plane.rotation.x=-Math.PI/2;//这会导致自由相机的视角发生bug??Y与Z轴混淆?
33             plane.isPickable=false;
34             plane.isVisible=false;
35             arr_mesh_groupicon.push(plane);
36             //plane.parent=mesh_arr_cards;
37         }
38     }

  建立了三个小绿球作为场景的参照物,建立了五个小平面作为分组标记,这五个标记暂时不可见(在调试分组标记的过程中Babylon.js发生了bug,相机输入的Y轴和Z轴发生混淆,但没有深入分析原因)。

  E、初始化事件

 1 function initEvent()
 2     {
 3         InitMouse();
 4         window.addEventListener("keydown", onKeyDown, false);//按键按下
 5         window.addEventListener("keyup", onKeyUp, false);//按键抬起
 6         window.addEventListener("resize", function () {
 7             if (engine) {
 8                 engine.resize();
 9             }
10         },false);
11     }

  InitMouse中是对鼠标的四种事件监听,具体代码在Control20180312.js文件中,接下来监听了按键按下、按键抬起、窗口尺寸变化。

  F、初始化UI

1 function initUI()
2     {
3         MakeFullUI();
4         //var advancedTexture = MyGame.fsUI;
5 
6     }

  代码主体在FullUI.js文件中

  G、初始化对象

1 function initObj()
2     {//添加75个(?)实验对象
3 
4         DrawCard4();
5         SortCard();
6     }

  具体代码在DrawCard.js中

  H、初始化渲染循环(也是逻辑循环)

 1 function initLoop()
 2     {
 3         var _this=MyGame;
 4         scene.registerBeforeRender(function() {     //比runRenderLoop更早
 5         });
 6         scene.registerAfterRender(
 7                 function() {
 8                     if(MyGame.flag_startr==1)//如果开始渲染了
 9                     {//如果正在使用相机网格进行漫游
10                         if(MyGame.player.prototype=CameraMesh&&MyGame.flag_view=="first_lock")
11                         {
12                             host20171018(MyGame.player);
13                         }
14                     }
15                 }
16         );
17 
18         engine.runRenderLoop(function ()        //场景逻辑和AI也从这里引入
19         {
20             if (divFps) {
21                 // Fps
22                 divFps.innerHTML = engine.getFps().toFixed() + " fps";
23             }
24             MyGame.HandleNoHurry();//这里包含了运动使用的计时器
25             if(_this.flag_startr==1||_this.flag_view!="first_pick")
26             {
27                 
登录查看全部

参与评论

评论留言

还没有评论留言,赶紧来抢楼吧~~

手机查看

返回顶部

给这篇文章打个标签吧~

棒极了 糟糕透顶 好文章 PHP JAVA JS 小程序 Python SEO MySql 确认