整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          手把手教你實現一個高性能的抽抽樂H5小游戲(含源碼)

          手把手教你實現一個高性能的抽抽樂H5小游戲(含源碼)

          我們就來學點有意思的,用幾十行代碼來實現一個高性能的抽獎小游戲.也基于此,來鞏固我們的javascript基礎,以及前端一些基本算法的應用.

          效果展示



          你將收獲

          • 防抖函數的應用
          • 用css實現九宮格布局
          • 生成n維環形坐標的算法
          • 如何實現環形隨機軌道運動函數
          • 實現加速度動畫
          • 性能分析與優化

          設計思路



          具體實現

          由于目前已有很多方案可以實現九宮格抽獎動畫,比如使用動態active實現邊框動畫,用隨機算法和定時器設置在何處停止等等. 為了進一步提高性能,本文介紹的方法,將使用坐標法,將操作dom的成本降低,完全由js實現滑塊的路徑的計算,滑塊元素采用絕對定位,讓其脫離文檔流,避免其他元素的重繪等等,最后點擊按鈕我們會使用防抖函數來避免頻繁執行函數,造成不必要的性能損失.

          1. 九宮格布局實現

          為了讓大家更加熟悉dom結構,這里我就不用js動態生成了.如下html結構:

          <div class="wrap">
              <div class="title">圣誕抽抽樂</div>
              <div class="box">
                  <div class="item">我愛你</div>
                  <div class="item">你愛我</div>
                  <div class="item">我不愛你</div>
                  <div class="item">你愛我</div>
                  <div class="item start">開始</div>
                  <div class="item">你愛我</div>
                  <div class="item">再見</div>
                  <div class="item">謝謝惠顧</div>
                  <div class="item">你愛我</div>
                  <div class="spin"></div>
              </div>
          </div>
          復制代碼

          九宮格布局我們使用flex來實現,核心代碼如下:

          .box {
              display: flex;
              flex-wrap: wrap;
              width: 300px;
              height: 300px;
              position: relative;
              .item {
                  box-sizing: border-box;
                  width: 100px;
              }
              // 滑塊
              .spin {
                  box-sizing: border-box;
                  position: absolute;
                  left: 0;
                  top: 0;
                  display: inline-block;
                  width: 100px;
                  height: 100px;
                  background-color: rgba(0,0,0,.2);
              }
          }
          復制代碼

          由上可知容器box采用flex布局,要想讓flex子元素換行,我們這里要設置flex-wrap: wrap;此時九宮格布局就實現了. 滑塊采用絕對定位,至于具體如何去沿著環形軌道運動,請繼續看下文介紹.

          2.生成n維環形坐標的算法


          由上圖我們可以知道,一個九宮格的4條邊,可以用以上8個坐標收尾連接起來,那么我們可以基于這個規律.來生成環形坐標集合.代碼如下:


          /**
           * 生成n維環形坐標
           * @param {number} n 維度
           * @param {number} cell 單位坐標長度
           */
          function generateCirclePath(n, cell) {
            let arr=[]
            for(let i=0; i< n; i++) {
                arr.push([i*cell, 0])
            }
            for(let i=0; i< n-1; i++) {
                arr.push([(n-1)*cell, (i+1)*cell])
            }
            for(let i=0; i< n-1; i++) {
                arr.push([(n-i-2)*cell, (n-1)*cell])
            }
            for(let i=0; i< n-2; i++) {
                arr.push([0, (n-i-2)*cell])
            }
            return arr
          }
          復制代碼

          如果是單位坐標,那么cell為1,cell設計的目的就位為了和現實的元素相結合,我們可以手動設置單元格的寬度來實現不同大小的n維環形坐標集.

          3.實現環形隨機軌道運動函數

          由抽獎動畫分析可知,我們滑塊運動的軌跡,其實就是環形坐標集合,所以我們只要讓滑塊的頂點(默認左上角)沿著環形坐標集合一步步變化就好了.

          function run(el, path, n=1, i=0, len=path.length) {
              setTimeout(()=> {
                  if(n > 0) {
                    if(len <=i) {
                        i=n===1 ? len : 0
                        n--
                    }
                    el.css('transform', `translate(${path[i][0]}px, ${path[i][1]}px)`)
                    run(el, path, n, ++i, len)
                  }
              }, 300)   
          }
          復制代碼

          這樣就能實現我們的滑塊按照九宮格邊框運動的動畫了,當然以上函數只是基本的動畫, 還沒有實現在隨機位置停止, 以及滑塊的加速度運動,這塊需要一定的技巧和js基礎知識比如閉包.

          3.1 加速度運動

          加速度運動其實很簡單,比如每轉過一圈將setTimeout的延遲時間改變即可.代碼如下:

          function run(el, path, n=1, speed=60, i=0, len=path.length) {
              setTimeout(()=> {
                  if(n > 0) {
                    if(len <=i) {
                        i=n===1 ? len : 0
                        n--
                        speed +=(300 - speed) / n
                    }
                    el.css('transform', `translate(${path[i][0]}px, ${path[i][1]}px)`)
                    run(el, path, n, speed, ++i, len)
                  }
              }, speed)   
          }
          復制代碼

          3.2 隨機停止實現

          隨機停止這塊主要是用了Math.random這個API, 我們在最后一圈的時候, 根據隨機返回的數值來決定何時停止,這里我們在函數內部實現隨機數值,完整代碼如下:

          /**
          * 環形隨機軌道運動函數
          * @param {element} el 運動的dom元素
          * @param {array} path 運動的環形坐標集合
          * @param {number} speed 運動的初始速度
          * @param {number} i 運動的初始位置
          * @param {number} len 路徑的長度
          * @param {number} random 中獎坐標
          */
          function run(el, path, n=1, speed=60, i=0, len=path.length, random=Math.floor(Math.random() * len)) {
              setTimeout(()=> {
                  if(n > 0) {
                    // 如果n為1,則設置中獎數值
                    if(n===1) {
                      len=random
                    }
                    if(len <=i) {
                        i=n===1 ? len : 0
                        n--
                        speed +=(300 - speed) / n
                    }
                    el.css('transform', `translate(${path[i][0]}px, ${path[i][1]}px)`)
                    run(el, path, n, speed, ++i, len, random)
                  }
              }, speed)   
          }
          復制代碼

          4.實現點擊開始的防抖函數以及應用

          防抖函數實現:

          // 防抖函數,避免頻繁點擊執行多次函數
          function debounce(fn, interval=300) {
            let timeout=null
            return function () {
                clearTimeout(timeout)
                timeout=setTimeout(()=> {
                    fn.apply(this, arguments)
                }, interval)
            }
          }
          復制代碼

          那么我們點擊時,代碼應該長這樣:

          // 點擊開始按鈕,開始抽獎
          $('.start').on('click',debounce(()=> { run($('.spin'), generateCirclePath(3, 100), 3) }))
          復制代碼

          延伸

          在文章發布之后,有熱心的小伙伴們提出了幾個建議,綜合如下:

          • 抽獎動畫結束后提供回調來通知頁面以便處理其他邏輯
          • 處理多次點擊時,雖然加了防抖,但是用戶在動畫沒結束時點擊了開始按鈕,又會執行動畫導致動畫越來越快,發生混亂.

          綜合以上問題,我在之前基礎上做了進一步擴展,來解決以上提到的問題.

          1. 添加動畫結束時回調:
          /**
          * 環形隨機軌道運動函數
          * @param {element} el 運動的dom元素
          * @param {array} path 運動的環形坐標集合
          * @param {func} cb 動畫結束時回調
          * @param {number} speed 運動的初始速度
          * @param {number} i 運動的初始位置
          * @param {number} len 路徑的長度
          * @param {number} random 中獎坐標
          */
          function run(el, path, n=1, cb, speed=60, i=0, len=path.length, random=Math.floor(Math.random() * len)) {
              setTimeout(()=> {
                  if(n > 0) {
                    // 如果n為1,則設置中獎數值
                    if(n===1) {
                      len=random
                    }
                    if(len <=i) {
                        i=n===1 ? len : 0
                        n--
                        speed +=(300 - speed) / n
                    }
                    el.css('transform', `translate(${path[i][0]}px, ${path[i][1]}px)`)
                    run(el, path, n, cb, speed, ++i, len, random)
                  }else {
                    cb && cb()
                  }
              }, speed)
          }
          復制代碼
          1. 處理多次點擊時,雖然加了防抖,但是用戶在動畫沒結束時點擊了開始按鈕,又會執行動畫導致動畫越來越快,發生混亂.
          // 1. 點擊開始按鈕,開始抽獎
          $('.start').on('click',debounce(()=> {
              // 點擊開始后禁用點擊
              $('.start').css('pointer-events', 'none')
              run($('.spin'), generateCirclePath(3, 100), 3, ()=> {
                // 動畫結束后開啟按鈕點擊
                $('.start').css('pointer-events', 'auto')
                alert('抽獎結束')
              }) 
          }))
          復制代碼

          謝謝各位認真的建議,繼續優化吧.

          總結

          該實現方式的好處是支持n維環形坐標的抽獎,基于坐標法的應用還有很多,尤其是游戲和圖形領域,在實現過程中一定要考慮性能和可擴展性,這樣我們就可以在不同場景使用同一套方法論,豈不樂哉?本文完整源碼我會放在github上,歡迎交流學習~

          github地址:https://github.com/MrXujiang?tab=repositories

          歡迎在公眾號《趣談前端》加入我們一起學習討論,共同探索前端的邊界。

          天給大家分享了一個用javascript和HTML5做出來的音樂播放器,今天小編我要給大家做一個童年小霸王游戲機里面的經典游戲,坦克大戰,源碼全部都有,希望大家自己也能夠多練習,將知識變為自己的。

          這里還是要說一下我的前端學習群:594959296,從我一個人到現在的1422人都是我每篇文章每個特效聚集的小伙伴,可以說都是我們大前端的學霸啊,不定期分享干貨。想學到東西的都可以來,歡迎初學和進階中的小伙伴

          完整代碼太長,要自己練習的加這個群:594959296 所有源碼都上傳到群文件了,自助下載學習,之前也上傳了很多類似源碼,希望大家能早日成大神

          學習javascript也是有門檻的,就是你的html和css至少還比較熟練,您不能連html這東東是干啥的都不知道就開始學javascript了,學乘除前,學好加減法總是有益無害的。

          再說二點建議:

          1. 不要急著看一些復雜的javascript網頁特效的代碼,這樣除了打擊你的自信心,什么也學不到

          2. 看網上什么幾天精通javascript的,直接跳過吧,沒戲

          如果想看到更加系統的文章和學習方法經驗可以關注我的微信公眾號:‘web前端課程’關注后回復‘給我資料’可以領取一套完整的學習視頻


          覽器現在為 JavaScript 開發人員提供了許多用于創建有趣站點的選項。 Flash曾經被用來做這個 - 它很流行,無數的游戲、播放器、花哨的界面等等都是在它上面創造出來的。但是,它們不再在任何現代瀏覽器中運行。

          Flash技術重量級,漏洞百出,因此開始放棄。特別是因為有 HTML5 形式的替代方案。

          Canvas 是可以使用 JS 命令在其上進行繪制的畫布。它可用于創建動畫背景、各種構造函數,最重要的是,游戲。

          在本文中,您將學習如何使用 JavaScript 和 HTML5 創建瀏覽器游戲。但首先,我們建議您熟悉 JS 中的面向對象編程(只需了解什么是類、方法和對象)。這是創建游戲的最佳方式,因為它允許您使用實體而不是抽象數據。但是,有一個缺點:任何版本的 Internet Explorer 都不支持 OOP。


          游戲頁面布局

          首先,您需要創建一個顯示畫布的頁面。這需要很少的 HTML:

          <!DOCTYPE html>
          <html>
              <head>
                  <title>JS Game</title>
                  <link rel="stylesheet" href="style.css">
                  <meta charset="utf-8">
              </head>
              <body>
                  <div class="wrapper">
                      <canvas width="0" height="0" class="canvas" id="canvas">Your browser does not support JavaScript и HTML5 </canvas>
                  </div>
                  <script src="game.js"></script>
              </body>
          </html>

          現在我們需要添加樣式:

          body, html
          {
              width: 100%;
              height: 100%;
              padding: 0px;
              margin: 0px;
              overflow: hidden;
          }
           
          .wrapper
          {
              width: 100%;
              height: 100%;
          }
           
          .canvas
          {
              width: 100%;
              height: 100%;
              background: #000;
          }

          請注意,在 HTML 中,canvas 元素的寬度和高度為零,而 CSS 指定為 100%。 在這方面,畫布的行為就像一個圖像。 它具有實際分辨率和可見分辨率。

          使用樣式更改可見分辨率。 但是,圖片的尺寸將保持不變:它只會被拉伸或壓縮。 這就是為什么稍后將通過腳本指定實際寬度和高度的原因。


          游戲腳本

          首先,讓我們為游戲添加一個腳本藍圖:

          var canvas=document.getElementById("canvas"); //Retrieving a canvas from the DOM
          var ctx=canvas.getContext("2d"); //Obtaining a context - through it you can work with the canvas
           
          var scale=0.1; //Machine scale
           
          Resize(); //When the page loads, the canvas size is set
           
          window.addEventListener("resize", Resize); //Changing the size of the window will change the size of the canvas
           
          window.addEventListener("keydown", function (e) { KeyDown(e); }); //Receiving keystrokes from the keyboard
           
          var objects=[]; //An array of game objects
          var roads=[]; //An array with backgrounds
           
          var player=null; //The object controlled by the player - here will be the number of the object in the objects array
           
          function Start()
          {
              timer=setInterval(Update, 1000 / 60); //The game state will update 60 times per second - at this rate, the update of what is happening will seem very smooth
          }
           
          function Stop()
          {
              clearInterval(timer); //Stopping the update
          }
           
          function Update() //Game update
          {
              Draw();
          }
           
          function Draw() //Working with graphics
          {
              ctx.clearRect(0, 0, canvas.width, canvas.height); //Clearing the canvas from the previous frame
          }
           
          function KeyDown(e)
          {
              switch(e.keyCode)
              {
                  case 37: //Left
                      break;
           
                  case 39: //Right
                      break;
           
                  case 38: //Up
                      break;
           
                  case 40: //Down
                      break;
           
                  case 27: //Esc
                      break;
              }
          }
           
          function Resize()
          {
              canvas.width=window.innerWidth;
              canvas.height=window.innerHeight;
          }

          該腳本包含創建游戲所需的一切:數據(數組)、更新、繪制和控制功能。 它只剩下用基本邏輯來補充它。 也就是說,準確指定對象的行為方式以及它們在畫布上的顯示方式。


          游戲邏輯

          在 Update() 函數調用期間,游戲對象的狀態將發生變化。 之后,它們將使用 Draw() 函數在畫布上繪制。 所以我們實際上并沒有在畫布上移動對象,我們繪制它們一次,然后更改它們的坐標,擦除舊圖像并使用新坐標顯示對象。 這一切發生得如此之快,以至于給人一種運動的錯覺。

          讓我們看一個道路的例子。

          此圖像顯示在畫布上并逐漸向下移動。 緊接著,又會顯示出另一幅這樣的畫面,讓人感覺像是一條沒有盡頭的路。

          為此,讓我們創建一個 Road 類:

          class Road
          {
              constructor(image, y)
              {
                  this.x=0;
                  this.y=y;
           
                  this.image=new Image();
                  
                  this.image.src=image;
              }
           
              Update(road) 
              {
                  this.y +=speed; //The image shifts down when you refresh
           
                  if(this.y > window.innerHeight) //If the image has gone over the edge of the canvas, change the position
                  {
                      this.y=road.y - this.image.height + speed; //The new position is indicated with the second background
                  }
              }
          }

          將 Road 類的兩個對象添加到背景數組中:

          var roads=[
              new Road("images/road.jpg", 0),
              new Road("images/road.jpg", 626)
          ]; //background array

          您現在可以更改 Update() 函數,以便圖像的位置隨每一幀而變化。

          function Update() //Game Update
          {
              roads[0].Update(roads[1]);
              roads[1].Update(roads[0]);
           
              Draw();
          }

          只需添加這些圖像的輸出:

          function Draw() //Working with graphics
          {
              ctx.clearRect(0, 0, canvas.width, canvas.height); //Clearing the canvas from the previous frame
           
              for(var i=0; i < roads.length; i++)
              {
                  ctx.drawImage
                  (
                      roads[i].image, //Render image
                      0, //Initial X position in the image
                      0, //Initial Y-axis position in the image
                      roads[i].image.width, //Image width
                      roads[i].image.height, //Image height
                      roads[i].x, //X-axis position on the canvas
                      roads[i].y, //Y-axis position on the canvas
                      canvas.width, //The width of the image on the canvas
                      canvas.width //Since the width and height of the background are the same, the width is specified as the height
                  );
              }
          }

          現在你可以看到它在游戲中是如何工作的:

          現在是添加玩家和 NPC 的時候了。 為此,您需要編寫一個 Car 類。 它將有一個 Move() 方法,玩家可以使用該方法控制他的汽車。 NPC 的移動將通過 Update() 完成,它只是更改 Y 坐標。

          class Car
          {
              constructor(image, x, y)
              {
                  this.x=x;
                  this.y=y;
           
                  this.image=new Image();
           
                  this.image.src=image;
              }
           
              Update()
              {
                  this.y +=speed;
              }
           
              Move(v, d) 
              {
                  if(v=="x") //X-axis movement
                  {
                      this.x +=d; //Offset
           
                      //
                      if(this.x + this.image.width * scale > canvas.width)
                      {
                          this.x -=d; 
                      }
              
                      if(this.x < 0)
                      {
                          this.x=0;
                      }
                  }
                  else //Y-axis movement
                  {
                      this.y +=d;
           
                      if(this.y + this.image.height * scale > canvas.height)
                      {
                          this.y -=d;
                      }
           
                      if(this.y < 0)
                      {
                          this.y=0;
                      }
                  }
                  
              }
          }

          讓我們創建第一個要檢查的對象。

          var objects=[
              new Car("images/car.png", 15, 10)
          ]; //An array of game objects
          var player=0; //the number of the object controlled by the player

          現在您需要向 Draw() 函數添加一個用于繪制汽車的命令。

          for(var i=0; i < objects.length; i++)
          {
              ctx.drawImage
              (
                  objects[i].image, //Render image
                  0, //Initial X position in the image
                  0, //Initial Y-axis position in the image
                  objects[i].image.width, //Image width
                  objects[i].image.height, //Image height
                  objects[i].x, //X-axis position on the canvas
                  objects[i].y, //Y-axis position on the canvas
                  objects[i].image.width * scale, //The width of the image on the canvas multiplied by the scale
                  objects[i].image.height * scale //The height of the image on the canvas multiplied by the scale
              );
          }

          在按下鍵盤時調用的 KeyDown() 函數中,您需要添加對 Move() 方法的調用。

          function KeyDown(e)
          {
              switch(e.keyCode)
              {
                  case 37: //Left
                      objects[player].Move("x", -speed);
                      break;
          
                  case 39: //Right
                      objects[player].Move("x", speed);
                      break;
           
                  case 38: //Up
                      objects[player].Move("y", -speed);
                      break;
           
                  case 40: //Down
                      objects[player].Move("y", speed);
                      break;
           
                  case 27: //Esc
                      if(timer==null)
                      {
                          Start();
                      }
                      else
                      {
                          Stop();
                      }
                      break;
              }
          }

          現在您可以檢查渲染和控制。

          碰撞時什么都沒有發生,但這將在以后修復。 首先,您需要確保刪除視圖中丟失的對象。 這是為了避免堵塞 RAM。

          在 Car 類中,我們添加值為 false 的字段 dead,然后在 Update() 方法中對其進行更改:

          if(this.y > canvas.height + 50)
          {
              this.dead=true;
          }

          現在您需要更改游戲的更新功能,替換與對象關聯的代碼:

          var hasDead=false;
           
          for(var i=0; i < objects.length; i++)
          {
              if(i !=player)
              {
                  objects[i].Update();
           
                  if(objects[i].dead)
                  {
                      hasDead=true;
                  }
              }
          }
           
          if(hasDead)
          {
              objects.shift();
          }

          如果您不移除物體,當生成太多汽車時,游戲將開始降低計算機速度。


          游戲物體碰撞

          現在您可以開始實施碰撞。 為此,請為 Car 類編寫一個方法 Collide(),它將檢查汽車的坐標:

          Collide(car)
          {
              var hit=false;
           
              if(this.y < car.y + car.image.height * scale && this.y + this.image.height * scale > car.y) //If the objects are on the same line horizontally
              {
                  if(this.x + this.image.width * scale > car.x && this.x < car.x + car.image.width * scale) //If the objects are on the same line vertically
                  {
                      hit=true;
                  }
              }
           
              return hit;
          }

          現在我們需要在 Update() 函數中添加碰撞檢查:

          var hit=false;
           
          for(var i=0; i < objects.length; i++)
          {
              if(i !=player)
              {
                  hit=objects[player].Collide(objects[i]);
           
                  if(hit)
                  {
                      alert("You crashed!");
                      Stop();
                      break;
                  }
              }
          }

          這是游戲中的內容

          碰撞時可以添加任何邏輯:

          ? 打開動畫;

          ? 添加效果;;

          ? 刪除對象;

          ? 健康狀況的改變,等等。

          所有這些都由開發人員自行決定。


          結論

          這是一個非常簡單的游戲,但足以了解 JS 如何處理圖形以及一般如何創建游戲。 您可以在 GitHub 存儲庫中找到圖像和完整的游戲代碼。

          使用畫布非常適合處理圖形:它提供了很棒的功能并且不會過多地加載瀏覽器。 我們現在也有一個可用的 WebGL 庫(示例和用法),它可以為您提供大量的性能和 3D 工作(canvas 無法做到這一點)。

          理解 WebGL 可能很困難——也許相反,許多人對嘗試 Unity 引擎更感興趣,它知道如何編譯項目以在瀏覽器中運行它們。


          關注七爪網,獲取更多APP/小程序/網站源碼資源!


          主站蜘蛛池模板: 骚片AV蜜桃精品一区| 日韩精品视频一区二区三区 | 无码一区二区三区| 日韩精品无码一区二区中文字幕 | 国产无套精品一区二区| 精品国产天堂综合一区在线| 色噜噜狠狠一区二区三区果冻| 中文字幕亚洲一区二区三区| 国产SUV精品一区二区88| 日韩一区二区在线视频| 亚洲国产成人久久综合一区| 夜色福利一区二区三区| 国产精品亚洲一区二区三区在线| 亚洲一区二区三区无码影院| 日本一区二区三区精品国产 | 久久精品午夜一区二区福利| 亚洲一区二区三区香蕉| 农村人乱弄一区二区| 亚洲男人的天堂一区二区| 亚洲AV成人精品日韩一区18p| 日韩久久精品一区二区三区| 精品久久久久久中文字幕一区| 日韩一区二区三区射精| 国语对白一区二区三区| 国产乱码精品一区二区三区四川| 夜夜精品视频一区二区| 美女视频一区二区| 国产成人精品一区二区三区| 中文字幕一区二区三区有限公司| 久久99精品免费一区二区| 狠狠综合久久AV一区二区三区| av无码一区二区三区| 国产亚洲一区二区三区在线| 少妇无码一区二区三区| tom影院亚洲国产一区二区| 成人精品一区二区不卡视频| 视频一区二区在线观看| 真实国产乱子伦精品一区二区三区| 日韩电影一区二区| 在线精品视频一区二区| 无码AV动漫精品一区二区免费|