<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          前端游戲巨制! CSS居然可以做3D游戲了

          共 30843字,需瀏覽 62分鐘

           ·

          2021-09-12 08:57

          點擊上方 前端瓶子君,關(guān)注公眾號

          回復(fù)算法,加入前端編程面試算法每日一題群


          前言

          偶然接觸到CSS的3D屬性, 就萌生了一種做3D游戲的想法.

          了解過css3D屬性的同學(xué)應(yīng)該都了解過perspective、perspective-origintransform-style: preserve-3d這個三個屬性值, 它們構(gòu)成了CSS的3d世界.

          同時, 還有transform屬性來對3D的節(jié)點進(jìn)行平移、縮放、旋轉(zhuǎn)以及拉伸.

          屬性值很簡單, 在我們平時的web開發(fā)中也很少用到.

          那用這些CSS3D屬性可以做3D游戲嗎?

          當(dāng)然是可以的.

          即使只有沙盒, 也有我的世界這種神作.

          今天我就來帶大家玩一個從未有過的全新3D體驗.

          廢話不多說, 我們先來看下效果:

          這里是試玩地址pc端暢玩[1]

          我們要完成這個迷宮大作戰(zhàn),需要完成以下步驟:

          1. 創(chuàng)建一個3D世界
          2. 寫一個3D相機的功能
          3. 創(chuàng)建一座3D迷宮
          4. 創(chuàng)建一個可以自由運動的玩家
          5. 在迷宮中找出一條最短路徑提示

          我們先來看下一些前置知識.

          做一款CSS3D游戲需要的知識和概念

          CSS3D坐標(biāo)系

          在css3D中, 首先要明確一個概念, 3D坐標(biāo)系.
          使用左手坐標(biāo)系, 伸出我們的左手, 大拇指和食指成L狀, 其他手指與食指垂直, 如圖:

          WechatIMG315.png

          大拇指為X軸, 食指為Y軸, 其他手指為Z軸.
          這個就是CSS3D中的坐標(biāo)系.

          透視屬性

          perspective為css中的透視屬性.

          這個屬性是什么意思呢, 可以把我們的眼睛看作觀察點, 眼睛到目標(biāo)物體的距離就是視距, 也就是這里說的透視屬性.

          大家都知道, 「透視」+「2D」= 「3D」.

          perspective: 1200px;
          -webkit-perspective:  1200px;
          復(fù)制代碼

          3D相機

          在3D游戲開發(fā)中, 會有相機的概念, 即是人眼所見皆是相機所見.
          在游戲中場景的移動, 大部分都是移動相機.
          例如賽車游戲中, 相機就是跟隨車子移動, 所以我們才能看到一路的風(fēng)景.
          在這里, 我們會使用CSS去實現(xiàn)一個偽3d相機.

          變換屬性

          在CSS3D中我們對3D盒子做平移、旋轉(zhuǎn)、拉伸、縮放使用transform屬性.

          • translateX 平移X軸
          • translateY 平移Y軸
          • translateZ 平移Z軸
          • rotateX 旋轉(zhuǎn)X軸
          • rotateY 旋轉(zhuǎn)Y軸
          • rotateZ 旋轉(zhuǎn)Z軸
          • rotate3d(x,y,z,deg) 旋轉(zhuǎn)X、Y、Z軸多少度

          注意:
          這里「先平移再旋轉(zhuǎn)」和「先旋轉(zhuǎn)再平移」是不一樣的
          旋轉(zhuǎn)的角度都是角度值.

          這里還有不清楚的同學(xué)可以參閱羽飛的這篇[juejin.cn/post/699769…[2]] 附帶有demo

          矩陣變換

          我們完成游戲的過程中會用到矩陣變換.
          在js中, 獲取某個節(jié)點的transform屬性, 會得到一個矩陣, 這里我打印一下, 他就是長這個樣子:

          var _ground = document.getElementsByClassName("ground")[0];
          var bg_style = document.defaultView.getComputedStyle(_ground, null).transform;
          console.log("矩陣變換---->>>",bg_style)
          復(fù)制代碼
          WechatIMG307.png

          那么我們?nèi)绾问褂镁仃嚾ゲ僮鱰ransform呢?

          在線性變換中, 我們都會去使用矩陣的相乘.
          CSS3D中使用4*4的矩陣進(jìn)行3D變換.
          下面的矩陣我均用二維數(shù)組表示.
          例如matrix3d(1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,1)可以用二維數(shù)組表示:

          [
              [1000],
              [0100],
              [0010],
              [0001]
          ]
          復(fù)制代碼

          平移即使使用原來狀態(tài)的矩陣和以下矩陣相乘, dx, dy, dz分別是移動的方向x, y, z.

          [
              [100, dx],
              [010, dy],
              [001, dz],
              [0001]
          ]
          復(fù)制代碼

          繞X軸旋轉(zhuǎn)??, 即是與以下矩陣相乘.

          [
              [1000],
              [0, cos??, sin??, 0],
              [0, -sin??, cos??, 0],
              [0001]
          ]
          復(fù)制代碼

          繞Y軸旋轉(zhuǎn)??, 即是與以下矩陣相乘.

          [
              [cos??, 0, -sin??, 0],
              [0100],
              [sin??, 0, cos??, 0],
              [0001]
          ]
          復(fù)制代碼

          繞Z軸旋轉(zhuǎn)??, 即是與以下矩陣相乘.

          [
              [cos??, sin??, 00],
              [-sin??, cos??, 00],
              [0010],
              [0001]
          ]
          復(fù)制代碼

          具體的矩陣的其他知識這里講了, 大家有興趣可以自行下去學(xué)習(xí).
          我們這里只需要很簡單的旋轉(zhuǎn)應(yīng)用.

          開始創(chuàng)建一個3D世界

          我們先來創(chuàng)建UI界面.

          • 相機div
          • 地平線div
          • 棋盤div
          • 玩家div(這里是一個正方體)

          注意
          正方體先旋轉(zhuǎn)在平移, 這種方法應(yīng)該是最簡單的.
          一個平面繞X軸、Y軸旋轉(zhuǎn)180度、±90度, 都只需要平移Z軸.
          這里大家試過就明白了.

          我們先來看下html部分:

              <div class="camera">
                  <!-- 地面 -->
                  <div class="ground">
                      <div class="box">
                          <div class="box-con">
                              <div class="wall">z</div>
                              <div class="wall">z</div>
                              <div class="wall">y</div>
                              <div class="wall">y</div>
                              <div class="wall">x</div>
                              <div class="wall">x</div>
                              <div class="linex"></div>
                              <div class="liney"></div>
                              <div class="linez"></div>
                          </div>
                          <!-- 棋盤 -->
                          <div class="pan"></div>
                      </div>
                  </div>
              </div>
          復(fù)制代碼

          很簡單的布局, 其中linex、liney、linez是我畫的坐標(biāo)軸輔助線.
          紅線為X軸, 綠線為Y軸, 藍(lán)線為Z軸. 接著我們來看下正方體的主要CSS代碼.

          ...
            .box-con{
                width50px;
                height50px;
                transform-style: preserve-3d;
                transform-origin50% 50%;
                transformtranslateZ(25px) ;
                transition: all 2s cubic-bezier(0.0750.820.1651);
            }
            .wall{
                width100%;
                height100%;
                border1px solid #fdd894;
                background-color#fb7922;
              
            }
            .wall:nth-child(1) {
                transformtranslateZ(25px);
            }
            .wall:nth-child(2) {
                transformrotateX(180degtranslateZ(25px);
            }
            .wall:nth-child(3) {
                transformrotateX(90degtranslateZ(25px);
            }
            .wall:nth-child(4) {
                transformrotateX(-90degtranslateZ(25px);
            }
            .wall:nth-child(5) {
                transformrotateY(90degtranslateZ(25px);
            }
            .wall:nth-child(6) {
                transformrotateY(-90degtranslateZ(25px);
            }
          復(fù)制代碼

          粘貼一大堆CSS代碼顯得很蠢.
          其他CSS這里就不粘貼了, 有興趣的同學(xué)可以直接下載源碼查看. 界面搭建完成如圖所示:

          WechatIMG308.png

          接下來就是重頭戲了, 我們?nèi)慾s代碼來繼續(xù)完成我們的游戲.

          完成一個3D相機功能

          相機在3D開發(fā)中必不可少, 使用相機功能不僅能查看3D世界模型, 同時也能實現(xiàn)很多實時的炫酷功能.

          一個3d相機需要哪些功能?

          最簡單的, 上下左右能夠360度無死角觀察地圖.同時需要拉近拉遠(yuǎn)視距.

          通過鼠標(biāo)交互

          鼠標(biāo)左右移動可以旋轉(zhuǎn)查看地圖; 鼠標(biāo)上下移動可以觀察上下地圖; 鼠標(biāo)滾輪可以拉近拉遠(yuǎn)視距.

          ? 1. 監(jiān)聽鼠標(biāo)事件

          首先, 我們需要通過監(jiān)聽鼠標(biāo)事件來記錄鼠標(biāo)位置, 從而判斷相機上下左右查看.

              /** 鼠標(biāo)上次位置 */
              var lastX = 0, lastY = 0;
                /** 控制一次滑動 */
              var isDown = false;
                /** 監(jiān)聽鼠標(biāo)按下 */
              document.addEventListener("mousedown", (e) => {
                  lastX = e.clientX;
                  lastY = e.clientY;
                  isDown = true;
              });
                  /** 監(jiān)聽鼠標(biāo)移動 */
              document.addEventListener("mousemove", (e) => {
                  if (!isDown) return;
                  let _offsetX = e.clientX - lastX;
                  let _offsetY = e.clientY - lastY;
                  lastX = e.clientX;
                  lastY = e.clientY;
                  //判斷方向
                  var dirH = 1, dirV = 1;
                  if (_offsetX < 0) {
                      dirH = -1;
                  }
                  if (_offsetY > 0) {
                      dirV = -1;
                  }
              });
              document.addEventListener("mouseup", (e) => {
                  isDown = false;
              });
          復(fù)制代碼

          ? 2. 判斷相機上下左右

          使用perspective-origin來設(shè)置相機的上下視線.
          使用transform來旋轉(zhuǎn)Z軸查看左右方向上的360度.

          /** 監(jiān)聽鼠標(biāo)移動 */
              document.addEventListener("mousemove", (e) => {
                  if (!isDown) return;
                  let _offsetX = e.clientX - lastX;
                  let _offsetY = e.clientY - lastY;
                  lastX = e.clientX;
                  lastY = e.clientY;
                  var bg_style = document.defaultView.getComputedStyle(_ground, null).transform;
                  var camera_style = document.defaultView.getComputedStyle(_camera, null).perspectiveOrigin;
                  var matrix4 = new Matrix4();
                  var _cy = +camera_style.split(' ')[1].split('px')[0];
                  var str = bg_style.split("matrix3d(")[1].split(")")[0].split(",");
                  var oldMartrix4 = str.map((item) => +item);
                  var dirH = 1, dirV = 1;
                  if (_offsetX < 0) {
                      dirH = -1;
                  }
                  if (_offsetY > 0) {
                      dirV = -1;
                  }
                  //每次移動旋轉(zhuǎn)角度
                  var angleZ = 2 * dirH;
                  var newMartri4 = matrix4.set(Math.cos(angleZ * Math.PI / 180), -Math.sin(angleZ * Math.PI / 180), 00Math.sin(angleZ * Math.PI / 180), Math.cos(angleZ * Math.PI / 180), 0000100001);
                  var new_mar = null;
                  if (Math.abs(_offsetX) > Math.abs(_offsetY)) {
                      new_mar = matrix4.multiplyMatrices(oldMartrix4, newMartri4);
                  } else {
                      _camera.style.perspectiveOrigin = `500px ${_cy + 10 * dirV}px`;
                  }
                  new_mar && (_ground.style.transform = `matrix3d(${new_mar.join(',')})`);
              });
          復(fù)制代碼

          這里使用了矩陣的方法來旋轉(zhuǎn)Z軸, 矩陣類Matrix4是我臨時寫的一個方法類, 就倆方法, 一個設(shè)置二維數(shù)組matrix4.set, 一個矩陣相乘matrix4.multiplyMatrices.
          文末的源碼地址中有, 這里就不再贅述了.

          ? 3. 監(jiān)聽滾輪拉近拉遠(yuǎn)距離

          這里就是根據(jù)perspective來設(shè)置視距.

          //監(jiān)聽滾輪
          document.addEventListener('mousewheel', (e) => {
              var per = document.defaultView.getComputedStyle(_camera, null).perspective;
              let newper = (+per.split("px")[0] + Math.floor(e.deltaY / 10)) + "px";
              _camera.style.perspective = newper
          }, false);
          復(fù)制代碼

          注意:
          perspective-origin屬性只有X、Y兩個值, 做不到和u3D一樣的相機.
          我這里取巧使用了對地平線的旋轉(zhuǎn), 從而達(dá)到一樣的效果.
          滾輪拉近拉遠(yuǎn)視距有點別扭, 和3D引擎區(qū)別還是很大.

          完成之后可以看到如下的場景, 已經(jīng)可以隨時觀察我們的地圖了.

          index1.gif

          這樣子, 一個3D相機就完成, 大家有興趣的可以自己下去寫一下, 還是很有意思的.

          繪制迷宮棋盤

          繪制格子地圖最簡單了, 我這里使用一個15*15的數(shù)組.
          0」代表可以通過的路, 「1」代表障礙物.

          var grid = [
              000100000100010,
              010100101011010,
              100001000000001,
              000100000100101,
              001010010101000,
              000011000001000,
              001000100100010,
              110010010001000,
              101001010100010,
              000011000000000,
              101001010001001,
              010010000000010,
              110010010101000,
              101001000000100,
              001010100110000
          ];
          復(fù)制代碼

          然后我們?nèi)ケ闅v這個數(shù)組, 得到地圖.
          寫一個方法去創(chuàng)建地圖格子, 同時返回格子數(shù)組和節(jié)點數(shù)組.
          這里的block是在html中創(chuàng)建的一個預(yù)制體, 他是一個正方體.
          然后通過克隆節(jié)點的方式添加進(jìn)棋盤中.

          /** 棋盤 */
          function pan() {
              const con = document.getElementsByClassName("pan")[0];
              const block = document.getElementsByClassName("block")[0];
              let elArr = [];
              grid.forEach((item, index) => {
                  let r = Math.floor(index / 15);
                  let c = index % 15;
                  const gezi = document.createElement("div");
                  gezi.classList = "pan-item"
                  // gezi.innerHTML = `${r},${c}`
                  con.appendChild(gezi);
                  var newBlock = block.cloneNode(true);
                  //障礙物
                  if (item == 1) {
                      gezi.appendChild(newBlock);
                      blockArr.push(c + "-" + r);
                  }
                  elArr.push(gezi);
              });
              const panArr = arrTrans(15, grid);
              return { elArr, panArr };
          }
          const panData = pan();
          復(fù)制代碼

          可以看到, 我們的界面已經(jīng)變成了這樣.

          WechatIMG310.png

          接下來, 我們需要去控制玩家移動了.

          控制玩家移動

          通過上下左右w s a d鍵來控制玩家移動.
          使用transform來移動和旋轉(zhuǎn)玩家盒子.

          ? 監(jiān)聽鍵盤事件

          通過監(jiān)聽鍵盤事件onkeydown來判斷key值的上下左右.

          document.onkeydown = function (e{
              /** 移動物體 */
              move(e.key);
          }
          復(fù)制代碼

          ? 進(jìn)行位移

          在位移中, 使用translate來平移, Z軸始終正對我們的相機, 所以我們只需要移動X軸和Y軸.
          聲明一個變量記錄當(dāng)前位置.
          同時需要記錄上次變換的transform的值, 這里我們就不繼續(xù)矩陣變換了.

          /** 當(dāng)前位置 */
          var position = { x0y0 };
          /** 記錄上次變換值 */
          var lastTransform = {
              translateX'0px',
              translateY'0px',
              translateZ'25px',
              rotateX'0deg',
              rotateY'0deg',
              rotateZ'0deg'
          };
          復(fù)制代碼

          每一個格子都可以看成是二維數(shù)組的下標(biāo)構(gòu)成, 每次我們移動一個格子的距離.

           switch (key) {
              case 'w':
                  position.y++;
                  lastTransform.translateY = position.y * 50 + 'px';
                  break;
              case 's':
                  position.y--;
                  lastTransform.translateY = position.y * 50 + 'px';
                  break;
              case 'a':
                  position.x++;
                  lastTransform.translateX = position.x * 50 + 'px';
                  break;
              case 'd':
                  position.x--;
                  lastTransform.translateX = position.x * 50 + 'px';
                  break;
          }
          //賦值樣式
          for (let item in lastTransform) {
              strTransfrom += item + '(' + lastTransform[item] + ') ';
          }
          target.style.transform = strTransfrom;
          復(fù)制代碼

          到這里, 我們的玩家盒子已經(jīng)可以移動了.

          注意
          在css3D中的平移可以看成是世界坐標(biāo).
          所以我們只需要關(guān)心X、Y軸. 而不需要去移動Z軸. 即使我們進(jìn)行了旋轉(zhuǎn).

          ? 在移動的過程中進(jìn)行旋轉(zhuǎn)

          在CSS3D中, 3D旋轉(zhuǎn)和其他3D引擎中不一樣, 一般的諸如u3D、threejs中, 在每次旋轉(zhuǎn)完成之后都會重新校對成世界坐標(biāo), 相對來說 就很好計算繞什么軸旋轉(zhuǎn)多少度.

          然而, 筆者也低估了CSS3D的旋轉(zhuǎn).
          我以為上下左右滾動一個正方體很簡單. 事實并非如此.

          CSS3D的旋轉(zhuǎn)涉及到四元數(shù)和萬向鎖.

          比如我們旋轉(zhuǎn)我們的玩家盒子. 如圖所示:

          首先, 第一個格子(0,0)向上繞X軸旋轉(zhuǎn)90度, 就可以到達(dá)(1.0); 向左繞Y軸旋轉(zhuǎn)90度, 可以到達(dá)(0,1); 那我們是不是就可以得到規(guī)律如下:

          WechatIMG312.png

          如圖中所示, 單純的向上下, 向左右繞軸旋轉(zhuǎn)沒有問題, 但是要旋轉(zhuǎn)到紅色的格子, 兩種不同走法, 到紅色的格子之后旋轉(zhuǎn)就會出現(xiàn)兩種可能. 從而導(dǎo)致旋轉(zhuǎn)出錯.

          同時這個規(guī)律雖然難尋, 但是可以寫出來, 最重要的是, 按照這個規(guī)律來旋轉(zhuǎn)CSS3D中的盒子, 是不對的

          那有人就說了, 這不說的屁話嗎?

          經(jīng)過筆者實驗, 倒是發(fā)現(xiàn)了一些規(guī)律. 我們繼續(xù)按照這個規(guī)律往下走.

          • 旋轉(zhuǎn)X軸的時候, 同時看當(dāng)前Z軸的度數(shù), Z軸為90度的奇數(shù)倍, 旋轉(zhuǎn)Y軸, 否則旋轉(zhuǎn)X軸.
          • 旋轉(zhuǎn)Y軸的時候, 同時看當(dāng)前Z軸的度數(shù), Z軸為90度的奇數(shù)倍, 旋轉(zhuǎn)X軸, 否則旋轉(zhuǎn)Z軸.
          • 旋轉(zhuǎn)Z軸的時候, 繼續(xù)旋轉(zhuǎn)Z軸

          這樣子我們的旋轉(zhuǎn)方向就搞定了.

          if (nextRotateDir[0] == "X") {
              if (Math.floor(Math.abs(lastRotate.lastRotateZ) / 90) % 2 == 1) {
                  lastTransform[`rotateY`] = (lastRotate[`lastRotateY`] + 90 * dir) + 'deg';
              } else {
                  lastTransform[`rotateX`] = (lastRotate[`lastRotateX`] - 90 * dir) + 'deg';
              }
          }
          if (nextRotateDir[0] == "Y") {
              if (Math.floor(Math.abs(Math.abs(lastRotate.lastRotateZ)) / 90) % 2 == 1) {
                  lastTransform[`rotateX`] = (lastRotate[`lastRotateX`] + 90 * dir) + 'deg';
              } else {
                  lastTransform[`rotateZ`] = (lastRotate[`lastRotateZ`] + 90 * dir) + 'deg';
              }
          }
          if (nextRotateDir[0] == "Z") {
              lastTransform[`rotate${nextRotateDir[0]}`] = (lastRotate[`lastRotate${nextRotateDir[0]}`] - 90 * dir) + 'deg';
          }
          復(fù)制代碼

          然而, 這還沒有完, 這種方式的旋轉(zhuǎn)還有個坑, 就是我不知道該旋轉(zhuǎn)90度還是-90度了.
          這里并不是簡單的上下左右去加減.

          旋轉(zhuǎn)方向?qū)α? 旋轉(zhuǎn)角度不知該如何計算了.

          具體代碼可以查看源碼[3].

          彩蛋時間

          ?????? 同時這里會伴隨著「萬向鎖」的出現(xiàn), 即是Z軸與X軸重合了. 哈哈哈哈~
          ?????? 這里筆者還沒有解決, 也希望萬能的網(wǎng)友能夠出言幫忙~
          ?????? 筆者后續(xù)解決了會更新的. 哈哈哈哈, 大坑.

          好了, 這里問題不影響我們的項目. 我們繼續(xù)講如何找到最短路徑并給出提示.

          最短路徑的計算

          在迷宮中, 從一個點到另一個點的最短路徑怎么計算呢? 這里筆者使用的是廣度優(yōu)先遍歷(BFS)算法來計算最短路徑.

          我們來思考:

          1. 二維數(shù)組中找最短路徑
          2. 每一格的最短路徑只有上下左右相鄰的四格
          3. 那么只要遞歸尋找每一格的最短距離直至找到終點

          這里我們需要使用「隊列」先進(jìn)先出的特點.

          我們先來看一張圖:

          WechatIMG313.png

          很清晰的可以得到最短路徑.

          注意
          使用兩個長度為4的數(shù)組表示上下左右相鄰的格子需要相加的下標(biāo)偏移量.
          每次入隊之前需要判斷是否已經(jīng)入隊了.
          每次出隊時需要判斷是否是終點.
          需要記錄當(dāng)前入隊的目標(biāo)的父節(jié)點, 方便獲取到最短路徑.

          我們來看下代碼:

          //春初路徑
          var stack = [];
          /**
           * BFS 實現(xiàn)尋路
           * @param {*} grid 
           * @param {*} start {x: 0,y: 0}
           * @param {*} end {x: 3,y: 3}
           */

          function getShortPath(grid, start, end, a{
              let maxL_x = grid.length;
              let maxL_y = grid[0].length;
              let queue = new Queue();
              //最短步數(shù)
              let step = 0;
              //上左下右
              let dx = [10-10];
              let dy = [010-1];
              //加入第一個元素
              queue.enqueue(start);
              //存儲一個一樣的用來排查是否遍歷過
              let mem = new Array(maxL_x);
              for (let n = 0; n < maxL_x; n++) {
                  mem[n] = new Array(maxL_y);
                  mem[n].fill(100);
              }
              while (!queue.isEmpty()) {
                  let p = [];
                  for (let i = queue.size(); i > 0; i--) {
                      let preTraget = queue.dequeue();
                      p.push(preTraget);
                      //找到目標(biāo)
                      if (preTraget.x == end.x && preTraget.y == end.y) {
                          stack.push(p);
                          return step;
                      }
                      //遍歷四個相鄰格子
                      for (let j = 0; j < 4; j++) {
                          let nextX = preTraget.x + dx[j];
                          let nextY = preTraget.y + dy[j];

                          if (nextX < maxL_x && nextX >= 0 && nextY < maxL_y && nextY >= 0) {
                              let nextTraget = { x: nextX, y: nextY };
                              if (grid[nextX][nextY] == a && a < mem[nextX][nextY]) {
                                  queue.enqueue({ ...nextTraget, f: { x: preTraget.x, y: preTraget.y } });
                                  mem[nextX][nextY] = a;
                              }
                          }
                      }
                  }
                  stack.push(p);
                  step++;
              }
          }
          /* 找出一條最短路徑**/
          function recall(end{
              let path = [];
              let front = { x: end.x, y: end.y };
              while (stack.length) {
                  let item = stack.pop();
                  for (let i = 0; i < item.length; i++) {
                      if (!item[i].f) break;
                      if (item[i].x == front.x && item[i].y == front.y) {
                          path.push({ x: item[i].x, y: item[i].y });
                          front.x = item[i].f.x;
                          front.y = item[i].f.y;
                          break;
                      }
                  }
              }
              return path;
          }

          復(fù)制代碼

          這樣子我們就可以找到一條最短路徑并得到最短的步數(shù).
          然后我們繼續(xù)去遍歷我們的原數(shù)組(即棋盤原數(shù)組).
          點擊提示點亮路徑.

          var step = getShortPath(panArr, { x0y0 }, { x14y14 }, 0);
          console.log("最短距離----", step);
          _perstep.innerHTML = `請在<span>${step}</span>步內(nèi)走到終點`;
          var path = recall({ x14y14 });
          console.log("路徑---", path);
          /** 提示 */
          var tipCount = 0;
          _tip.addEventListener("click", () => {
              console.log("9999", tipCount)
              elArr.forEach((item, index) => {
                  let r = Math.floor(index / 15);
                  let c = index % 15;
                  path.forEach((_item, i) => {
                      if (_item.x == r && _item.y == c) {
                          // console.log("ooo",_item)
                          if (tipCount % 2 == 0)
                              item.classList = "pan-item pan-path";
                          else
                              item.classList = "pan-item";
                      }
                  })
              });
              tipCount++;
          });
          復(fù)制代碼

          這樣子, 我們可以得到如圖的提示:

          WechatIMG314.png

          大功告成. 嘿嘿, 是不是很驚艷的感覺~

          尾聲

          當(dāng)然, 我這里的這個小游戲還有可以完善的地方 比如:

          • 可以增加道具, 拾取可以減少已走步數(shù)
          • 可以增加配置關(guān)卡
          • 還可以增加跳躍功能
          • ...

          原來如此, CSS3D能做的事還有很多, 怎么用全看自己的想象力有多豐富了.

          哈哈哈, 真想用CSS3D寫一個「我的世界」玩玩, 性能問題恐怕會有點大.

          本文例子均在PC端體驗較好.

          試玩地址[4]

          源碼地址[5]

          歡迎大家拍磚指正, 筆者功力尚淺, 如有不當(dāng)之處請斧正!

          關(guān)于本文

          作者:起小就些熊

          https://juejin.cn/post/7000963575573381134


          最后

          歡迎關(guān)注【前端瓶子君】??ヽ(°▽°)ノ?
          回復(fù)「算法」,加入前端編程源碼算法群,每日一道面試題(工作日),第二天瓶子君都會很認(rèn)真的解答喲!
          回復(fù)「交流」,吹吹水、聊聊技術(shù)、吐吐槽!
          回復(fù)「閱讀」,每日刷刷高質(zhì)量好文!
          如果這篇文章對你有幫助,在看」是最大的支持
           》》面試官也在看的算法資料《《
          “在看和轉(zhuǎn)發(fā)”就是最大的支持????
          瀏覽 47
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青青草原免费在线视频 | 夜夜国自一区 1080P | 欧美成人靠逼小视频 | 加勒比在线精品视频 | 亚洲中文在线播放 |