preface

Abstract: 2D Tetris has been playing rotten, whim made a 3D game machine, used to play Tetris… The basic idea is to first implement a Tetris mini-game in 2D, then use 3D modeling to create a 3D arcade model, and finally paste the 2D mini-game onto the 3D model. (PS: At last, the expanded part realizes the combination of video and 3D model)

www.hightopo.com/demo/tetris…

Code implementation

First, finish the 2D mini-game

In the process of checking the official documents, I learned that the component parameters of HT are stored in ht.datamodel () object. After loading the DataModel in view, various effects are presented.

gameDM = new ht.DataModel(); // Initialize the data model g2d = new hT.graph.graphView (gameDM); // Initialize the 2d view g2d.addtodom (); // Create a view on the pageCopy the code

Start creating the game model

  • First, let’s create a box for the game to define the scope of the game. Ht. Node is the base class for graphView rendering Node primitors, which can display images and support a variety of predefined graphics. So I’m going to use this class to create four rectangles and use them for scoping the game.
var lineNode = new ht.Node(); Linenode. s({"shape": "rect", // rectangle "shape.background": "#D8D8D8", // set the background color "shape.border. 1, // border width 1 "shape.border-color ": "#979797" // border color}); lineNode.setPosition(x, y); Linenode.setsize (width, height); linenode.setsize (width, height); Gamedm.add (lineNode); gamedm.add (lineNode); // Add the set meta information to the data modelCopy the code

 

X :552, y:111, width:704, height:22

Now that we have the top of the border, let’s create three more edges to form the frame:

x:211, y:562, width:22, width:880
x:893, y:562, width:22, width:880
x:552, y:1013, width:704, width:22Copy the code

The results are as follows:

Borders are almost complete, 4 borders can be dragged while browsing. Next change the method of border initialization:

Linenode. s({"shape": "rect", // rectangle "shape.background": "#D8D8D8", // set the background color "shape.border. 1, // border width 1 "shape.border. Color ": "#979797", // border color" 2d.editable" : false, // whether to edit "2d.movable" : False, // whether "2d.selectable" : false // whether selectable});Copy the code
  • To generate squares, my idea is to generate multiple squares, combine them into the graphics we need, and place them in the corresponding positions through the calculation of coordinates:

After the square is generated, the graph is rotated. There are two schemes. The first is to store the graph coordinates after the graph is flipped in order in the array, and each time the shape is changed, the first group or the last group of coordinates in the array is taken to change the shape. The second way is to use the HT.block () object to combine the corresponding primitives into a whole, and just choose 90° in the corresponding direction when deforming. In this case, I chose the second option, which looks like this:

function createUnit(x, y) { var node = new ht.Node(); node.s({ "shape": "rect", "shape.background": "#D8D8D8", "shape.border.width": 1, "shape.border.color": "#979797" }); node.setPosition(x, y); node.setSize(44, 44); gameDM.add(node); return node; } var block = new ht.Block(); block.addChild(createUnit(552, 133)); block.addChild(createUnit(552, 89)); block.addChild(createUnit(508, 133)); block.addChild(createUnit(596, 133)); Block. SetAnchor (0.5, 0.75); SetPosition (552, 144); // Set the center position of the combination, which will be installed when rotated to perform block.setPosition(552, 144);Copy the code

Block setting center point Anchor as shown below:





When setting up the rotation, just use the setRotation function to rotate the block:

block.setRotation(Math.PI*rotationNum/2); //rotationNum is a counter that keeps the number of rotations, ensuring that each rotation is 90° over the previous oneCopy the code

 

  • We have the cube, now it’s time to get it moving. Set timer, make the box drop a certain distance at intervals, and add keyboard monitoring events, so as to achieve W: flip, S: left move, D: right move, S: move down the operation, at the same time, in order not to make the box move out of the boundary, in each displacement will be a verification of the coordinate:
var offset = 44; var intervalTime = 1000; var topX = 552; var topY = 111; var leftSize = 211, rightSize = 882, bottomSize = 1002; var rotationNum = 0; Window. addEventListener('keydown', function(e){var index = 0; Var maxY = null; If (e.keycode == 87){// up w rotationNum ++; Block. The setRotation (Math. * rotationNum PI / 2); if (! CheckRotation (block)) {rotationNum -; Block. The setRotation (Math. * rotationNum PI / 2); }} else if (e.keycode == 65) {// left a moveBlock('x', -offset, block); } else if (e.keycode == 68) {// right d moveBlock('x', offset, block); } else if(e.keycode == 83){// down s moveBlock('y', offset, block); } }, false); SetInterval (function () {if (! MoveBlock ("y", offset, block) {rotationNum = 0; Block = createNode(blockType); // Generate a new block blockType = parseInt(math.random ()*100%5); // Next generated block graph}}, intervalTime); Function moveBlock(axis, offset, block){var ids = []; Var yindexs = []; Var indexArr = new Array(); for(var i = 0; i < block.size(); I ++){var childNode = block.getChildAt(I); Var childx = childNode. GetPosition (). X. Var childy = childNode. GetPosition (.) y; If (yindexs.indexof (childy) == -1) {yindexs.push(childy); } childx += offset;} childx += offset; }else if (axis === 'y') {childy += offset; } / / verification square whether move beyond the border the if (childx < leftSize | | childx > rightSize | | childy > bottomSize) {return false. } var obj = new Object(); Obj. X = childx; Obj. Y = childy; IndexArr. Push (obj); Ids. Push (childNode getId ()); } for(var j = 0; j < yindexs.length; J ++){var indexY = yindexs[J]; If (axis === 'y') {indexY += offset; } var nodeList = g2d.getDatasInRect({x:233, y:indexY, width:638, height:2}, True, false); If (nodelist. length > 0){var I = 0; i < nodeList.length; I ++){var x = nodelist.get (I).getPosition().x; Var y = nodeList. Get (I). GetPosition (.) y; Var id = nodeList. Get (I). The getId (); If (ids.indexof (id) > -1) {// Continue; } for (var k = 0; k < indexArr.length; K ++) {var obj = indexArr[k]; If (obj. X === x && obj. Y === y){// Return false; }}}}} var blockX = block.getx (); Var blockY = block. GetY (); If (axis === 'x') {blockX += offset; }else if (axis === 'y') {blockY += offset; } // The block moves to the new coordinates block.setPosition(blockX, blockY); return true; Function checkRotation(block){for(var I = 0; i < block.getChildren().length; I ++){var node = block.getChildAt(I); Var childx = node. GetPosition (). X. Var childy = node. GetPosition (.) y; / / it will turn of the graphics beyond the scope of the if (childx < leftSize | | childx > rightSize | | childy > bottomSize) {return false. } } return true; }Copy the code
  • After the displacement and deformation of the blocks, there is only one final step left in our mini-game: the elimination of the filled blocks. From the beginning, we knew that all the information was stored in the data model, so we eliminated the squares. Simply remove them from the data model as follows:
Function deleteBlock(block){var yindexs = []; Var num = 0; for(var i = 0; i < block.size(); I ++){var childNode = block.getChildAt(I); Var childy = childNode. GetPosition (.) y; Var nodeList = g2d.getDatasInRect({x:233, y:childy, width:638, height:2}, true, false); If (nodelist. length == 15) {for(var I = 0; i < nodeList.length; I++) {gameDM. Remove (nodeList. Get (I)); // Remove the corresponding primitives from the data model} num ++; Yindexs. Push (childy); }} if (yindexs. Length > 0) {for(var I = 0; i < yindexs.length; Var yindex = yindexs[I]; var yindex = yindexs[I]; Var h = yindex-133-offset; Var moveList = g2d.getDatasInRect({x:233, y:133, width:638, height:h}, true, false); Var mblock = new ht.block (); for(var i = 0; i < moveList.size(); I++) {mblock. AddChild (moveList. Get (I)); } moveBlock (' y ', offset, mblock); }}}Copy the code

To this, a simple Tetris small game to achieve. Of course, the game still has a lot to expand, such as: more block types, game score statistics, next prediction form, game background changes, etc. Let’s put that aside and move on to the next step.

Creating 3D models

According to the 3D modeling documentation, HT combines models with triangles.

  • First of all, the arcade model found on the Internet was split, and each module was divided into triangular faces:





As shown in the figure, the position of 0 was set as the origin (0,0,0). We opened the drawing tool to roughly estimate the position of each coordinate relative to the origin according to the ruler. The calculated coordinate array was imported into VS, and the combination of each triangle graph was imported into THE is vertex index coordinate:

Ht. Default. SetShape3dModel (' damBoard, {/ / name for the new model vs: [0, 0, 0, 0.23 / / 0, 0, 0, 0.23, 0.27, 0, 0.27, 0.28, 0, / / 3 0.27, 0.32, 0, 0.20, 0.33, 0. 0.18, 0.51, 0, / / 6, 0.27, 0.57, 0, 0.27, 0.655, 0, 0.20, 0.67, 0, / / 9 0, 0.535, 0], is: [0, 1, 2, 0, 2, 5, 2, 3, 4, 4, 2, 5, 5, 0, 10, 10, 5, 6, 6, 7, 8, 8, 6, 9, 9, 10, 6]});Copy the code

As with 2D, we create a base primitive for hT.node (), set to the name of our newly registered 3D model:

dataModel = new ht.DataModel(); g3d = new ht.graph3d.Graph3dView(dataModel); g3d.addToDOM(); var node = new ht.Node(); Node. s({'shape3d': 'damBoard', 'shape3d.reverse.flip': true, '3d.movable': false, '3d.editable': False, '3 d. Selectable: false}); node.p3([0, 20, 0]); node.s3([100, 100, 100]); dataModel.add(node);Copy the code





We already have a side, we can move the coordinate system along the Z axis to get the coordinate array of the other side, and then set the IS array according to the difference of each side. After combining all the sides, we will get a preliminary arcade model:

vs: [0, 0, 0, 0.23 / / 0, 0, 0, 0.23, 0.27, 0, 0.27, 0.28, 0, / / 3 0.27, 0.32, 0, 0.20, 0.33, 0, 0.18, 0.51, 0. / / 6 0.27, 0.57, 0, 0.27, 0.655, 0, 0.20, 0.67, 0, / / 9 0, 0.535, 0, 0, 0, 0.4, 0.23 / / 11, 0, 0.4, 0.23, 0.27, 0.4, 0.27, 0.28, 0.4, / / 14, 0.27, 0.32, 0.4, 0.20, 0.33, 0.4, 0.18, 0.51, 0.4, / / 17, 0.27, 0.57, 0.4, 0.27, 0.655, 0.4, 0.20, 0.67, 0.4, / / 20 0, 0.535, 0.4,Copy the code

  • The model is not beautiful, we can map each face of the model. Refer to the description of uv parameters of the model in the document, we can know that uv corresponds to the offset of each vertex in the model in the picture, the upper left corner of the picture is (0, 0), and the lower right corner is (1,1), so we can set the map for each face. Such as:



Ht. Default. SetShape3dModel (' damBoard, {vs: vsArr, is: isArr, uv: [0, 1, 0.81, 1, 0.81, 0.42, 1, 0.4, 1, 0.36, 0.725, 0.34, 0.65, 0.26, 1, 0.16, 1, 0.03, 0.75, 0, 0, 0.22,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, Image: '/image/side1.jpg' // image address});Copy the code

 

Similarly, set uv for other surfaces, and the final effect is as follows:

  • The whole 3D model is already built, but we still need to add game buttons to the model. In the official document modeling function, we can see that there are already a number of wrapped graphics available for us to use. Here I chose to create the action button using the createRightTriangleModel method to create a right triangle, and the createSmoothSphereModel function to create the start button:
ht.Default.setShape3dModel('button', ht.Default.createRightTriangleModel(true, true));
ht.Default.setShape3dModel('startButton', ht.Default.createSmoothSphereModel(20, 20, 0, Math.PI * 2, 0, Math.PI));Copy the code

Generate buttons based on the registered model:

CreateKeyboard (' up ', [21.5, 52.5, 26], [0, - Math. PI / 4, 0]). CreateKeyboard ('down', [0, Math.PI * 4/4, 0]); CreateKeyboard ('left', [23.5, 52, 28], [0, math.pi / 4, 0]); CreateKeyboard ('right', [23.5, 52, 24], [0, Math.PI * 5/4, 0]); Function createStartButton() {var node = new ht.node (); Node. SetTag (' restart '); Node. s({'shape3d': 'startButton', 'shape3d.reverse.flip': true, 'shape3d.color': '#7ED321', '3d.movable': false, '3d.editable': false}); Node. P3 ([23.5, 52.5, 11]); Node.s3 ([3, 3, 3]); // Button magnification datamodel.add (node); Function createKeyboard(tag, p3, r3) {var node = new ht.node (); Node. SetTag (tag); Node. s({'shape3d': 'button', 'shape3d.reverse. Flip ': true, 'shape3d.color': 'red', '3d. False, '3 d. The editable: false}); node.p3(p3); Node.s3 ([1.5, 1.5, 1.5]); // Button magnify node. R3 (r3); Datamodel.add (node); }Copy the code

The final effect is as follows:

  • The setImage property can be used to register a Canvas graphic component, not only to set up normal images, but also to attach a 2D mini-game to a 3D model. A 2D view can getCanvas information via getCanvas().
ht.Default.setImage('gameScrn', g2d.getCanvas()); Ht. Default. SetShape3dModel (' SCRN, {vs: vsArr, is: isArr, uv: scrnUV, image: 'gameScrn' // uses registered 2D canvas information as picture texture information for the screen}); G2d. getWidth = function () {return 1000; } g2d.getHeight = function () { return 600; } g2d.getCanvas().dynamic = true; SetInterval (function () {node.iv(); setInterval(function () {node.iv(); // Each change requires a refresh of the arcade model on the next frame g2d.validateImpl(); }, 10); SetTimeout (function () {g2d.fitContent(true); }, 500);Copy the code

The effect is as follows:







  • On the 2D canvas, we have added keyboard events to the game, now we just need to bind methods for each of the 5 buttons on the 3D model:
G3d. mi(function (e) {addInteractorListener if (e.type === 'clickData') {var tag = E.d ata. GetTag (); If (tag === 'restart') {gameAgain(node); } if (start) {if (tag === 'up') {block.setRotation(math.pi * (1 + rotationNum) / 2); RotationNum++; if (! CheckRotation (block)) {// Edge deformations limit rotationNum--; Math.pi * rotationNum / 2); }} else if (tag === 'down') {moveBlock('y', offset, block); } else if (tag === 'left') {moveBlock('x', -offset, block); } else if (tag === 'right') {moveBlock('x', offset, block); }}}});Copy the code

Basically complete the function of playing games in 3D arcade.

www.hightopo.com/demo/tetris…

expand

The above is just a simple application. Since WE can paste 2D canvas onto 3D, can we also paste video onto it?

The implementation code is as follows:

<video ID ="video1" width="270" autoplay SRC ="3D interactive. Mp4 "style="display:none"></video> var v = document.getElementById("video1"); var node = new ht.Node(); node.setSize(2200, 1100); gameDM.add(node); V.addeventlistener ('play', function () {var I = window.setInterval(function () {node.setimage (v); G2d.validateimpl (); // Refresh the 2D canvas g3d.invalidateData(box); // Refresh the arcade model in 3d drawings if (v.ded) {clearInterval(I)}}, 20); }, false);Copy the code

If you have any problems, you can leave a message or a private message or go to the official website (hightopo.com/) to consult relevant information.

conclusion

The video playback on the 3D model gave me great interest. If the picture of the camera can be transferred to the corresponding 3D scene, I believe that the video surveillance in some daily computer room monitoring, smart city and intelligent building will be more convenient and intuitive.