溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何用HTML 5打造斯諾克桌球俱樂部

發(fā)布時間:2021-10-12 16:29:53 來源:億速云 閱讀:177 作者:柒染 欄目:web開發(fā)

如何用HTML 5打造斯諾克桌球俱樂部,針對這個問題,這篇文章詳細(xì)介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

如何利用HTML5技術(shù)來打造一款非??岬乃怪Z克桌球游戲,文章中詳細(xì)地列出了開發(fā)的全過程,并解說了實現(xiàn)這個游戲的幾個關(guān)鍵點。

毫無疑問,我們已經(jīng)目睹了HTML5背后的那場偉大的Web開發(fā)革命。經(jīng)過那么多年HTML4的統(tǒng)治,一場全新的運動即將完全改變現(xiàn)在的Web世界。正是他釋放出來的現(xiàn)代化氣息和豐富的用戶體驗,讓它很快地成為了一個獨特的插件運行在類似Flash和Silverlight的框架之上。

如果你是一個非常年輕的開發(fā)者,也許你是剛剛在開始學(xué)習(xí)HTML5,所以可能你并沒有注意到他有太大的變化。在任何時候,我希望這篇文章能夠幫助到你,當(dāng)然,也希望像我一樣的老手能從中學(xué)到一些新的花樣。

你的點評對我來說非常重要,所以我很期待你的來信。當(dāng)然能讓我更興奮的是當(dāng)你在那個游戲畫面上右擊時暗暗地說一句“Hey,這居然不是Flash!也不是Silverlight!”

系統(tǒng)要求

想要使用本文提供的HTML5桌球應(yīng)用,你必須安裝下面的這些瀏覽器:Chrome 12, Internet Explorer 9 or Fire Fox 5

如何用HTML 5打造斯諾克桌球俱樂部

游戲規(guī)則

如何用HTML 5打造斯諾克桌球俱樂部

也許你已經(jīng)知道這是一個什么樣的游戲了,是的,這是“英式斯諾克”,實際上更確切的說是“簡易版英式斯諾克”,因為沒有實現(xiàn)所有的斯諾克游戲規(guī)則。你的目標(biāo)是按順序?qū)⒛繕?biāo)球灌入袋中,從而比其他選手得到更多的分?jǐn)?shù)。輪到你的時候,你就要出桿了:根據(jù)提示,你必須先打進(jìn)一個紅色球得到1分,如果打進(jìn)了,你就可以繼續(xù)打其他的球 - 但是這次你只能打彩色球了(也就是除紅色球以外的球)。如果成功打進(jìn),你將會得到各自彩球?qū)?yīng)的分?jǐn)?shù)。然后被打進(jìn)的彩球會回到球桌上,你可以繼續(xù)擊打其他的紅球。這樣周而復(fù)始,直到你失敗為止。當(dāng)你把所有的紅球都打完以后,球桌上就只剩下6個彩球了,你的目標(biāo)是將這6個彩球按以下順序依次打入袋中:黃(2分)、綠(3分)、棕(4分)、藍(lán)(5分)、粉(6分)、黑(7分)。如果一個球不是按上面順序打進(jìn)的,那它將會回到球桌上,否則,它最終會留在袋里。當(dāng)所有球都打完后,游戲結(jié)束,得分最多的人勝出。

犯規(guī)處理

為了處罰你的犯規(guī),其他選手將會得到你的罰分:

◆ 白球掉入袋中罰4分

◆ 白球第一次擊中的球是錯誤的話罰第一個球的分值

◆ 第一個錯誤的球掉入袋中罰第一個球的分值

◆ 處罰的分?jǐn)?shù)至少是4

下面的這段代碼展示了我是如何來計算犯規(guī)的:

var strokenBallsCount = 0;  console.log('strokenBalls.length: ' + strokenBalls.length);      for (var i = 0; i < strokenBalls.length; i++) {          var ball = strokenBalls[i];          //causing the cue ball to first hit a ball other than the ball on          if (strokenBallsCount == 0) {              if (ball.Points != teams[playingTeamID - 1].BallOn.Points) {                  if (ball.Points == 1 || teams[playingTeamID - 1].BallOn.Points == 1 ||                   fallenRedCount == redCount) {                      if (teams[playingTeamID - 1].BallOn.Points < 4) {                          teams[playingTeamID - 1].FoulList[teams[playingTeamID - 1]                          .FoulList.length] = 4;                          $('#gameEvents').append('  Foul 4 points :  Expected ' +                           teams[playingTeamID - 1].BallOn.Points + ', but hit ' + ball.Points);                      }                      else {                          teams[playingTeamID - 1].FoulList[teams[playingTeamID - 1]                          .FoulList.length] = teams[playingTeamID - 1].BallOn.Points;                          $('#gameEvents').append('  Foul ' + teams[playingTeamID - 1]                          .BallOn.Points + ' points :  Expected ' + teams[playingTeamID - 1]                          .BallOn.Points + ', but hit ' + ball.Points);                      }                      break;                  }              }          }             strokenBallsCount++;      }         //Foul: causing the cue ball to miss all object balls      if (strokenBallsCount == 0) {          teams[playingTeamID - 1].FoulList[teams[playingTeamID - 1].FoulList.length] = 4;          $('#gameEvents').append('  Foul 4 points :  causing the cue ball           to miss all object balls');      }         for (var i = 0; i < pottedBalls.length; i++) {          var ball = pottedBalls[i];          //causing the cue ball to enter a pocket          if (ball.Points == 0) {              teams[playingTeamID - 1].FoulList[teams[playingTeamID - 1].FoulList.length] = 4;              $('#gameEvents').append('  Foul 4 points :  causing the cue ball               to enter a pocket');          }          else {              //causing a ball different than the target ball to enter a pocket              if (ball.Points != teams[playingTeamID - 1].BallOn.Points) {                  if (ball.Points == 1 || teams[playingTeamID - 1].BallOn.Points == 1                   || fallenRedCount == redCount) {                      if (teams[playingTeamID - 1].BallOn.Points < 4) {                          teams[playingTeamID - 1].FoulList[teams[playingTeamID - 1]                          .FoulList.length] = 4;                          $('#gameEvents').append('  Foul 4 points : '                           + ball.Points + ' was potted, while ' + teams[playingTeamID - 1]                           .BallOn.Points + ' was expected');                          $('#gameEvents').append('  ball.Points: ' + ball.Points);                          $('#gameEvents').append('  teams[playingTeamID - 1]                          .BallOn.Points: ' + teams[playingTeamID - 1].BallOn.Points);                          $('#gameEvents').append('  fallenRedCount: ' + fallenRedCount);                          $('#gameEvents').append('  redCount: ' + redCount);                      }                      else {                          teams[playingTeamID - 1].FoulList[teams[playingTeamID - 1]                          .FoulList.length] = teams[playingTeamID - 1].BallOn.Points;                          $('#gameEvents').append('  Foul ' + teams[playingTeamID - 1]                          .BallOn.Points + ' points : ' + ball.Points + ' was potted, while '                           + teams[playingTeamID - 1].BallOn.Points + ' was expected');                      }                  }              }          }      }

得分

我們根據(jù)下面的規(guī)則來計算得分:紅(1分)、黃(2分)、綠(3分)、棕(4分)、藍(lán)(5分)、粉(6分)、黑(7分)。代碼如下:

if (teams[playingTeamID - 1].FoulList.length == 0) {             for (var i = 0; i < pottedBalls.length; i++) {                 var ball = pottedBalls[i];                 //legally potting reds or colors                 wonPoints += ball.Points;                 $('#gameEvents').append('  Potted +' + ball.Points + ' points.');             }         }         else {             teams[playingTeamID - 1].FoulList.sort();             lostPoints = teams[playingTeamID - 1].FoulList[teams[playingTeamID - 1].FoulList.length - 1];             $('#gameEvents').append('  Lost ' + lostPoints + ' points.');         }         teams[playingTeamID - 1].Points += wonPoints;         teams[awaitingTeamID - 1].Points += lostPoints;

選手的閃動動畫頭像

如何用HTML 5打造斯諾克桌球俱樂部

游戲是有兩位選手參與的,每一位選手都有自己的昵稱和頭像,選手的昵稱我們就簡單地以“player 1”和“player 2”來命名了(也許讓用戶自己輸入會更漂亮)。每位選手的頭像是一只正在打桌球的可愛小狗。當(dāng)輪到其中一位選手時,他的頭像就會有一閃一閃的動畫效果,同時對手的頭像會停止閃動。

這個效果我們是通過改變img元素的CSS3屬性opacity的值來實現(xiàn)的:我們使用jquery的animatio函數(shù)讓opacity的值在0-1.0之間變化。

function animateCurrentPlayerImage() {      var otherPlayerImageId = 0;      if (playingTeamID == 1)          otherPlayerImageId = 'player2Image';      else          otherPlayerImageId = 'player1Image';      var playerImageId = 'player' + playingTeamID + 'Image';      $('#' + playerImageId).animate({          opacity: 1.0      }, 500, function () {          $('#' + playerImageId).animate({              opacity: 0.0          }, 500, function () {              $('#' + playerImageId).animate({                  opacity: 1.0              }, 500, function () {              });          });      });         $('#' + otherPlayerImageId).animate({          opacity: 0.25      }, 1500, function () {      });  }

力量控制條

如何用HTML 5打造斯諾克桌球俱樂部

一個優(yōu)秀的斯諾克選手都能很好地把握住每一桿的力度.不同的技巧需要不同的擊球方式:直接的,間接的,或者利用邊角的等等。不同方向和不同力度的組合可以構(gòu)造成千上萬種可能的路徑。幸運的是,這個游戲提供了一個非常漂亮的力度控制條,可以幫助選手在擊球前調(diào)整他們的球桿。

為了達(dá)到這一點,我們使用了HTML5的meter元素標(biāo)簽,它可以完成測量距離的工作。meter標(biāo)簽最好在知道這次測量的最小值和最大值的情況下使用。在我們的這個例子中,這個值在0到100之間,因為IE9不支持meter,所以我用了一張背景圖來替代,這樣效果也是一樣的。

#strengthBar { position: absolute; margin:375px 0 0 139px;       width: 150px; color: lime; background-color: orange;       z-index: 5;}

當(dāng)你點擊了力度條后,你實際上是選擇了一個新的力度。一開始你可能不是很熟練,但在真實世界中,這是需要時間來訓(xùn)練自己的能力的。點擊力度條的代碼如下:

$('#strengthBar').click(function (e) {      var left = $('#strengthBar').css('margin-left').replace('px', '');      var x = e.pageX - left;      strength = (x / 150.0);      $('#strengthBar').val(strength * 100);  });
如何用HTML 5打造斯諾克桌球俱樂部

在當(dāng)前選手的頭像框里面,你會注意到有一個小球,我叫他“ball on”,就是當(dāng)前選手在規(guī)定時間內(nèi)應(yīng)該要擊打的那個球。如果這個球消失了,那選手將失去4分。同樣如果選手第一次擊中的球不是框內(nèi)顯示的球,那他也將失去4分。

這個“ball on”是直接將canvas元素覆蓋在用戶頭像上的,所以你在頭像上看到的那個球,他看起來像是在標(biāo)準(zhǔn)的div上蓋了一個img元素,但是這個球并不是img實現(xiàn)的。當(dāng)然我們也不能直接在div上畫圓弧和直線,這就是為什么我要將canvas覆蓋到頭像上的原因了??纯创a吧:

<canvas id="player1BallOn" class="player1BallOn">   </canvas>     <canvas id="player2BallOn" class="player2BallOn">   </canvas>
var player1BallOnContext = player1BallOnCanvas.getContext('2d');  var player2BallOnContext = player2BallOnCanvas.getContext('2d');  .  .  .  function renderBallOn() {      player1BallOnContext.clearRect(0, 0, 500, 500);      player2BallOnContext.clearRect(0, 0, 500, 500);      if (playingTeamID == 1) {          if (teams[0].BallOn != null)              drawBall(player1BallOnContext, teams[0].BallOn, new Vector2D(30, 120), 20);      }      else {          if (teams[1].BallOn != null)              drawBall(player2BallOnContext, teams[1].BallOn, new Vector2D(30, 120), 20);          player1BallOnContext.clearRect(0, 0, 133, 70);      }  }

旋轉(zhuǎn)屋頂上的電風(fēng)扇

在這個游戲中這把電風(fēng)扇純屬拿來玩玩有趣一把的。那為什么這里要放一把電風(fēng)扇?是這樣的,這個游戲的名字叫HTML5斯諾克俱樂部,放一把電風(fēng)扇就有俱樂部的氣氛了,當(dāng)然,我也是為了說明如何實現(xiàn)CSS3的旋轉(zhuǎn)。

實現(xiàn)這個非常簡單:首先我們需要一張PNG格式的電扇圖片。只是我們并沒有用電扇本身的圖片,我們用他的投影。通過顯示風(fēng)扇在球桌上的投影,讓我們覺得它在屋頂上旋轉(zhuǎn),這樣就達(dá)到了我們目的:

#roofFan { position:absolute; left: 600px; top: -100px; width: 500px; height: 500px;       border: 2px solid transparent; background-image: url('/Content/Images/roofFan.png');       background-size: 100%; opacity: 0.3; z-index: 2;}  .  .  .  <div id="roofFan"> </div>

為了獲得更為逼真的氣氛,我用Paint.Net軟件將電扇圖片平滑化了,現(xiàn)在你再也看不到電扇的邊緣了。我覺得這是達(dá)到如此酷的效果最為簡單的辦法。

如何用HTML 5打造斯諾克桌球俱樂部

除了用了這圖像處理的把戲,我們僅僅使用了一個帶背景圖的普通的div元素,這并沒有什么特別。既然我們已經(jīng)得到了電扇圖片,我們就要讓它開始旋轉(zhuǎn)了。這里我們使用CSS3的rotate屬性來實現(xiàn)這一切。

如何用HTML 5打造斯諾克桌球俱樂部

球桿動畫

如何用HTML 5打造斯諾克桌球俱樂部

球桿的動畫對于這個游戲也不是必需的,但是這的確為此添加了不少樂趣。當(dāng)你開始用鼠標(biāo)在球桌上移動時,你會注意到球桿的確是跟著你的鼠標(biāo)在轉(zhuǎn)動。這就是說球桿會一直保持跟隨鼠標(biāo)的移動,就像你身臨其境一般真實。因為選手只能用他的眼睛來瞄準(zhǔn),所以這個效果也會對選手有所幫助。

球桿是單獨一張PNG圖片,圖片本身不直接以img的形式展現(xiàn),也不以背景的形式展現(xiàn),相反,它是直接展現(xiàn)在一個專門的canvas上的。當(dāng)然我們也可以用div和css3來達(dá)到同樣的效果,但我覺得這樣能更好的說明如何在canvas上展現(xiàn)圖片。

首先,canvas元素會占據(jù)幾乎整個頁面的寬度。請注意這個特別的canvas有一個很大的z-index值,這樣球桿就可以一直在每個球的上方而不會被球遮蓋。當(dāng)你在球桌上移動鼠標(biāo)時,目標(biāo)點會實時更新,這時候球桿圖片會進(jìn)行2次轉(zhuǎn)換:首先,通過計算得到母球的位置,其次翻轉(zhuǎn)母球周圍的球桿,通過這2步我們就得到了鼠標(biāo)所在點和母球的中心點。

#cue { position:absolute; }  .  .  .  if (drawingtopCanvas.getContext) {      var cueContext = drawingtopCanvas.getContext('2d');  }  .  .  .  var cueCenter = [15, -4];  var cue = new Image;  cue.src = '<%: Url.Content("../Content/Images/cue.PNG") %>';     var shadowCue = new Image;  shadowCue.src = '<%: Url.Content("../Content/Images/shadowCue.PNG") %>';  cueContext.clearRect(0, 0, topCanvasWidth, topCanvasHeight);         if (isReady) {          cueContext.save();          cueContext.translate(cueBall.position.x + 351, cueBall.position.y + 145);          cueContext.rotate(shadowRotationAngle - Math.PI / 2);          cueContext.drawImage(shadowCue, cueCenter[0] + cueDistance, cueCenter[1]);          cueContext.restore();          cueContext.save();          cueContext.translate(cueBall.position.x + 351, cueBall.position.y + 140);          cueContext.rotate(angle - Math.PI / 2);          cueContext.drawImage(cue, cueCenter[0] + cueDistance, cueCenter[1]);          cueContext.restore();      }

為了讓球桿變得更真實我們?yōu)榍驐U添加了投影,并且我們故意讓球桿投影的旋轉(zhuǎn)角度和球桿的角度不一樣,我們這樣做是為了讓球桿有3D的效果。最終的效果實在是太酷了。

推拉球桿

如何用HTML 5打造斯諾克桌球俱樂部
如何用HTML 5打造斯諾克桌球俱樂部
如何用HTML 5打造斯諾克桌球俱樂部

這個球桿動畫模仿了真實人類的特征:你是否看到過斯諾克選手在瞄準(zhǔn)的時候會推拉球桿?我們通過HTML5改變母球和球桿的距離實現(xiàn)了這一效果。當(dāng)達(dá)到一個極點是球桿會被拉回來,然后到達(dá)另一個極點時又會被向前推。這樣周而復(fù)始,知道選手停止移動鼠標(biāo)。

var cueDistance = 0;  var cuePulling = true;  .  .  .          function render() {              .              .              .                 if (cuePulling) {                  if (lastMouseX == mouseX ||                  lastMouseY == mouseY) {                      cueDistance += 1;                  }                  else {                      cuePulling = false;                      getMouseXY();                  }              }              else {                     cueDistance -= 1;              }                 if (cueDistance > 40) {                  cueDistance = 40;                  cuePulling = false;              }              else if (cueDistance < 0) {                  cueDistance = 0;                  cuePulling = true;              }              .              .              .

顯示目標(biāo)路徑

如何用HTML 5打造斯諾克桌球俱樂部

當(dāng)選手移動鼠標(biāo)時,我們會在母球和當(dāng)前鼠標(biāo)點之間畫一條虛線。這對選手們長距離瞄準(zhǔn)相當(dāng)?shù)谋憷?/p>

這條目標(biāo)路徑只有在等待用戶擊球時才會顯示:

if (!cueBall.pocketIndex) {      context.strokeStyle = '#888';      context.lineWidth = 4;      context.lineCap = 'round';      context.beginPath();         //here we draw the line      context.dashedLine(cueBall.position.x, cueBall.position.y, targetX, targetY);         context.closePath();      context.stroke();  }

需要注意的是在HTML5 canvas中并沒有內(nèi)置函數(shù)來畫虛線。幸運的是有一個叫phrogz的家伙在StackOverflow網(wǎng)站上發(fā)布了一個關(guān)于這個畫虛線的帖子:

//function kindly provided by phrogz at:  //http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas  var CP = window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype;  if (CP && CP.lineTo) {      CP.dashedLine = function (x, y, x2, y2, dashArray) {          if (!dashArray) dashArray = [10, 5];          var dashCount = dashArray.length;          this.moveTo(x, y);          var dx = (x2 - x), dy = (y2 - y);          var slope = dy / dx;          var distRemaining = Math.sqrt(dx * dx + dy * dy);          var dashIndex = 0, draw = true;          while (distRemaining >= 0.1) {              var dashLength = dashArray[dashIndex++ % dashCount];              if (dashLength > distRemaining) dashLength = distRemaining;              var xStep = Math.sqrt(dashLength * dashLength / (1 + slope * slope));                 var signal = (x2 > x ? 1 : -1);                 x += xStep * signal;              y += slope * xStep * signal;              this[draw ? 'lineTo' : 'moveTo'](x, y);              distRemaining -= dashLength;              draw = !draw;          }      }  }

顯示跟蹤路徑

如何用HTML 5打造斯諾克桌球俱樂部

當(dāng)選手擊打母球后,母球會在球桌上留下一條跟蹤線,用來標(biāo)明其上一個點的位置。

創(chuàng)建這個跟蹤路徑比前面提到的目標(biāo)路徑復(fù)雜一點。首先我必須去實例化一個Queue對象,這個項目中的Queue對象原型由Stephen Morley提供。

var tracingQueue = new Queue();

一旦球開始運動,我們就將母球的實時位置壓入這個Queue中:

if (renderStep % 2 == 0) {      draw();      enqueuePosition(new Vector2D(cueBall.position.x, cueBall.position.y));  }

enqueuePosition函數(shù)確保了我們只保存前20個點的位置,這也就是為什么我們只讓顯示最近的母球的運動路徑的原因。

function enqueuePosition(position) {      tracingQueue.enqueue(position);      var len = tracingQueue.getLength();         if (len > 20) {          tracingQueue.dequeue();      }  }

接下來,我們要遍歷Queue中的數(shù)據(jù),從而來創(chuàng)建這條跟蹤路徑:

//drawing the tracing line  var lastPosX = cueBall.position.x;  var lastPosY = cueBall.position.y;     var arr = tracingQueue.getArray();     if (!cueBall.pocketIndex) {      context.strokeStyle = '#363';      context.lineWidth = 8;      context.lineCap = 'round';         context.beginPath();      var i = arr.length;      while (--i > -1) {          var posX = arr[i].x;          var posY = arr[i].y;          context.dashedLine(lastPosX, lastPosY, posX, posY, [10,200,10,20]);          lastPosX = posX;          lastPosY = posY;      }         context.closePath();      context.stroke();  }

繪制小球

如何用HTML 5打造斯諾克桌球俱樂部

小球和他們的投影都是呈現(xiàn)在一個特殊的canvas上(在球桿canvas下方)。

在呈現(xiàn)小球時,我們先要呈現(xiàn)其投影,這樣做主要是為了模擬3D的環(huán)境。每一個小球必須有投影,我們對每個小球的投影位置都會有一點細(xì)微的不同,這些細(xì)微差別表明了小球是在不同方向被投射的,也說明了光源所在的位置。

如何用HTML 5打造斯諾克桌球俱樂部

每個小球是由一個公共函數(shù)來畫的,函數(shù)有兩個參數(shù):1)canvas context;2)小球?qū)ο?。函?shù)先畫出一個完整的圓弧然后根據(jù)小球?qū)ο筇峁┑念伾珜⑦@個圓弧線性填充。

每一個小球?qū)ο笥?中顏色:光亮色、中色和暗色,這些顏色就是用來創(chuàng)建線性漸變顏色的,3D效果也是這樣做出來的。

function drawBall(context, ball, newPosition, newSize) {      var position = ball.position;      var size = ball.size;         if (newPosition != null)          position = newPosition;         if (newSize != null)          size = newSize;         //main circle      context.beginPath();      context.fillStyle = ball.color;      context.arc(position.x, position.y, size, 0, Math.PI * 2, true);         var gradient = context.createRadialGradient(          position.x - size / 2, position.y - size / 2, 0, position.x,          position.y, size );         //bright spot      gradient.addColorStop(0, ball.color);      gradient.addColorStop(1, ball.darkColor);      context.fillStyle = gradient;      context.fill();      context.closePath();         context.beginPath();      context.arc(position.x, position.y, size * 0.85, (Math.PI / 180) * 270,       (Math.PI / 180) * 200, true);      context.lineTo(ball.x, ball.y);      var gradient = context.createRadialGradient(          position.x - size * .5, position.y - size * .5,          0, position.x, position.y, size);         gradient.addColorStop(0, ball.lightColor);      gradient.addColorStop(0.5, 'transparent');      context.fillStyle = gradient;      context.fill();  }     function drawBallShadow(context, ball) {      //main circle      context.beginPath();      context.arc(ball.position.x + ball.size * .25, ball.position.y + ball.size * .25,       ball.size * 2, 0, Math.PI * 2, true);         try {          var gradient = context.createRadialGradient(              ball.position.x + ball.size * .25, ball.position.y + ball.size * .25,              0, ball.position.x + ball.size * .25, ball.position.y + ball.size * .25,              ball.size * 1.5 );      }      catch (err) {          alert(err);          alert(ball.position.x + ',' + ball.position.y);      }         gradient.addColorStop(0, '#000000');      gradient.addColorStop(1, 'transparent');      context.fillStyle = gradient;      context.fill();      context.closePath();  }

檢測小球之間的碰撞

如何用HTML 5打造斯諾克桌球俱樂部

小球以快速和連續(xù)的方式呈現(xiàn)在canvas上:首先,我們清空canvas,然后在上面繪制投影,再繪制小球,最后更新小球的位置坐標(biāo),這樣周而復(fù)始。在這個期間,我們需要檢查小球是否與另一個小球發(fā)生了碰撞,我們通過對小球的碰撞檢測來完成這些的。

function isColliding(ball1, ball2) {      if (ball1.pocketIndex == null && ball2.pocketIndex == null) {          var xd = (ball1.position.x - ball2.position.x);          var yd = (ball1.position.y - ball2.position.y);             var sumRadius = ball1.size + ball2.size;          var sqrRadius = sumRadius * sumRadius;             var distSqr = (xd * xd) + (yd * yd);             if (Math.round(distSqr) <= Math.round(sqrRadius)) {                 if (ball1.Points == 0) {                  strokenBalls[strokenBalls.length] = ball2;              }              else if (ball2.Points == 0) {                  strokenBalls[strokenBalls.length] = ball1;              }              return true;          }      }      return false;  }

解析小球之間的碰撞

如何用HTML 5打造斯諾克桌球俱樂部

上圖來自維基百科

我覺得解析小球間的碰撞問題是這個項目的核心,首先我們需要比較2個小球的組合(ball 1和ball 2)。然后我們找到一個“碰撞口”,也就是在碰撞的那一刻將它們移動到準(zhǔn)確的位置。要完成這些我們需要做一些矢量運算。下一步就是要計算最終碰撞的沖力,最后就是要改變兩個小球的沖量,也就是用它的沖力去加上或減去其速度向量得到的結(jié)果。當(dāng)碰撞結(jié)束后,它們的位置和速度都將發(fā)生變化。

function resolveCollision(ball1, ball2) {      // get the mtd (minimum translation distance)      var delta = ball1.position.subtract(ball2.position);      var r = ball1.size + ball2.size;      var dist2 = delta.dot(delta);         var d = delta.length();         var mtd = delta.multiply(((ball1.size + ball2.size + 0.1) - d) / d);         // resolve intersection --      // inverse mass quantities         var mass = 0.5;         var im1 = 1.0 / mass;      var im2 = 1.0 / mass;         // push-pull them apart based off their mass      if (!ball1.isFixed)          ball1ball1.position = ball1.position.add((mtd.multiply(im1 / (im1 + im2))));      if (!ball2.isFixed)          ball2ball2.position = ball2.position.subtract(mtd.multiply(im2 / (im1 + im2)));         // impact speed      var v = ball1.velocity.subtract(ball2.velocity);      var vvn = v.dot(mtd.normalize());         // sphere intersecting but moving away from each other already      //                if (vn > 0)      //                    return;         // collision impulse      var i = (-(0.0 + 0.08) * vn) / (im1 + im2);      var impulse = mtd.multiply(0.5);         var totalImpulse = Math.abs(impulse.x) + Math.abs(impulse.y);             //Do some collision audio effects here...         // change in momentum      if (!ball1.isFixed)          ball1ball1.velocity = ball1.velocity.add(impulse.multiply(im1));      if (!ball2.isFixed)          ball2ball2.velocity = ball2.velocity.subtract(impulse.multiply(im2));  }

檢測小球與轉(zhuǎn)角間的碰撞

咋眼看,要檢測小球與轉(zhuǎn)角之間的碰撞似乎有點復(fù)雜,但幸運的是有一個非常簡單卻有效的方法來解決這個問題:由于轉(zhuǎn)角也是圓形元素,我們可以把它們想象成固定的小球,如果我們能正確的確定固定小球的大小和位置,那么我們就像處理小球之間的碰撞那樣解決小球和轉(zhuǎn)角的碰撞問題。事實上,我們可以用同一個函數(shù)來完成這件事情,唯一的區(qū)別是這些轉(zhuǎn)角是固定不動的。

下圖是假設(shè)轉(zhuǎn)角都是一些小球,那就會這樣子:

如何用HTML 5打造斯諾克桌球俱樂部

分析小球與轉(zhuǎn)角之間的碰撞

如上面說到的那樣,小球之間的碰撞和小球與轉(zhuǎn)角的碰撞唯一不同的是后者我們要確保他保持固定不動,代碼如下:

function resolveCollision(ball1, ball2) {      .      .      .      // push-pull them apart based off their mass      if (!ball1.isFixed)          ball1ball1.position = ball1.position.add((mtd.multiply(im1 / (im1 + im2))));      if (!ball2.isFixed)          ball2ball2.position = ball2.position.subtract(mtd.multiply(im2 / (im1 + im2)));         .      .      .         // change in momentum      if (!ball1.isFixed)          ball1ball1.velocity = ball1.velocity.add(impulse.multiply(im1));      if (!ball2.isFixed)          ball2ball2.velocity = ball2.velocity.subtract(impulse.multiply(im2));  }

檢測小球與矩形邊緣的碰撞

我們通過小球與矩形邊緣的碰撞檢測來知道小球是否到達(dá)了球桌的上下左右邊緣。檢測的方式非常簡單:每個小球需要檢測4個點:我們通過對小球的x、y坐標(biāo)的加減來計算出這些點。然后將它們和我們定義的球桌矩形范圍進(jìn)行對比,看它們是否在這個范圍內(nèi)。

分析小球與矩形邊緣的碰撞

如何用HTML 5打造斯諾克桌球俱樂部

上圖來自維基百科

處理小球與矩形邊緣的碰撞比處理小球之間的碰撞簡單很多。我們需要在矩形邊界上找到離小球中心點最近的點,如果這個點在小球的半徑范圍內(nèi),那就說明碰撞了。

播放音頻

如何用HTML 5打造斯諾克桌球俱樂部

沒有一個游戲是沒有聲音的,不同的平臺處理音頻的方式不同。幸運的是HTML5給我們提供了一個audio標(biāo)簽,這簡化了我們定義音頻文件,加載音頻和調(diào)節(jié)音量的工作。

一般的HTML5例子都是給大家看audio的標(biāo)準(zhǔn)用法,就是展現(xiàn)一個播放控制條。在這個游戲中,我們使用了不同的方法,并隱藏了音頻播放控制條。這樣做是有道理的,因為音頻的播放不是直接由用戶控制的,而是由游戲中的事件觸發(fā)的。

頁面上一共有8個audio標(biāo)簽,其中6個小球碰撞的聲音,一個是擊打的聲音,一個則是小球掉入袋中的聲音。這些聲音可以同時播放,所以我們不用考慮并發(fā)的情況。

當(dāng)選手射擊母球時,我們就根據(jù)用戶選擇的力度來播放對應(yīng)音量的擊球聲音頻。

$('#topCanvas').click(function (e) {  .  .  .  audioShot.volume = strength / 100.0;  audioShot.play();  .  .  .  });

當(dāng)一個小球碰到了另一個小球,我們就計算出碰撞的強(qiáng)度,然后選擇合適音量的audio標(biāo)簽播放。

function resolveCollision(ball1, ball2) {      .      .      .      var totalImpulse = Math.abs(impulse.x) + Math.abs(impulse.y);         var audioHit;      var volume = 1.0;         if (totalImpulse > 5) {          audioHit = audioHit06;          volume = totalImpulse / 60.0;      }      else if (totalImpulse > 4) {          audioHit = audioHit05;          volume = totalImpulse / 12.0;      }      else if (totalImpulse > 3) {          audioHit = audioHit04;          volume = totalImpulse / 8.0;      }      else if (totalImpulse > 2) {          audioHit = audioHit03;          volume = totalImpulse / 5.0;      }      else {          audioHit = audioHit02;          volume = totalImpulse / 5.0;      }         if (audioHit != null) {          if (volume > 1)              volume = 1.0;             //audioHit.volume = volume;          audioHit.play();      }      .      .      .  }

最后,當(dāng)小球掉入袋中,我們就播放“fall.mp3”這個文件:

function pocketCheck() {      for (var ballIndex = 0; ballIndex < balls.length; ballIndex++) {             var ball = balls[ballIndex];             for (var pocketIndex = 0; pocketIndex < pockets.length; pocketIndex++) {              .              . some code here...              .                              if (Math.round(distSqr) < Math.round(sqrRadius)) {                  if (ball.pocketIndex == null) {                      ball.velocity = new Vector2D(0, 0);                         ball.pocketIndex = pocketIndex;                         pottedBalls[pottedBalls.length] = ball;                         if (audioFall != null)                          audioFall.play();                  }              }          }      }  }

本地存儲游戲狀態(tài)

有時候我們叫它web存儲或者DOM存儲,本地存儲HTML5定義的一種機(jī)制,用來保持本地數(shù)據(jù)。文章開頭提到的那幾種瀏覽器原生就支持本地存儲,所以我們不需要使用額外的js框架。

我們使用本地存儲主要用來保存用戶的游戲狀態(tài)。簡而言之,我們是要允許用戶在開始游戲一段時間后,關(guān)閉瀏覽器,第二天打開還能繼續(xù)往下玩。

當(dāng)游戲開始后,我們需要檢索在本地是否有數(shù)據(jù)存儲著,有的話就加載它們:

jQuery(document).ready(function () {      ...      retrieveGameState();      ...

另一方面,游戲開始后我們需要對每一次射擊的數(shù)據(jù)進(jìn)行保存。

function render() {      ...      processFallenBalls();      saveGameState();      ...  }

本地存儲是由一個字符串字典實現(xiàn)的。這個簡單的結(jié)構(gòu)體接受傳入的字符串和數(shù)字。我們只需要用setItem來將數(shù)據(jù)存儲到本地。下面的代碼說明了我們是如存儲時間數(shù)據(jù),小球位置坐標(biāo)數(shù)據(jù),選手?jǐn)?shù)據(jù)和當(dāng)前擊球選手與等待擊球選手的id:

function saveGameState() {      //we use this to check whether the browser supports local storage      if (Modernizr.localstorage) {          localStorage["lastGameSaveDate"] = new Date();          lastGameSaveDate = localStorage["lastGameSaveDate"];          localStorage.setItem("balls", $.toJSON(balls));          localStorage.setItem("teams", $.toJSON(teams));          localStorage.setItem("playingTeamID", playingTeamID);          localStorage.setItem("awaitingTeamID", awaitingTeamID);      }  }

我覺得除了下面的部分,上面的代碼都已經(jīng)解釋了自己的作用了:

localStorage.setItem("balls", $.toJSON(balls));  localStorage.setItem("teams", $.toJSON(teams));

目前為止,本地存儲還不能工作,我們需要將它們字符化,上面的2行代碼是利用了jquery的toJSON方法將復(fù)雜的對象轉(zhuǎn)換成了json字符串。

[{"isFixed":false,"color":"#ff0000","lightColor":"#ffffff","darkColor":"#400000","bounce":0.5,  "velocity":{"x":0,"y":0},"size":10,"position":{"x":190,"y":150},"pocketIndex":null,"points":1,  "initPosition":{"x":190,"y":150},"id":0},{"isFixed":false,"color":"#ff0000","lightColor":"#ffffff",  "darkColor":"#400000","bounce":0.5,"velocity":{"x":0,"y":0},"size":10,"position":{"x":172,"y":138},  "pocketIndex":null,"points":1,"initPosition":{"x":172,"y":138},"id":1},........

一旦我們將這些對象序列化到本地存儲后,我們就可以用類似的方法將它們檢索出來,我們現(xiàn)在就是用getItem方法來檢索他們。

function retrieveGameState() {      //we use this to check whether the browser supports local storage      if (Modernizr.localstorage) {             lastGameSaveDate = localStorage["lastGameSaveDate"];          if (lastGameSaveDate) {              var jsonBalls = $.evalJSON(localStorage.getItem("balls"));              balls = [];                 var ballsOnTable = 0;                 for (var i = 0; i < jsonBalls.length; i++) {                  var jsonBall = jsonBalls[i];                  var ball = {};                  ball.position = new Vector2D(jsonBall.position.x, jsonBall.position.y);                  ball.velocity = new Vector2D(0, 0);                     ball.isFixed = jsonBall.isFixed;                  ball.color = jsonBall.color;                  ball.lightColor = jsonBall.lightColor;                  ball.darkColor = jsonBall.darkColor;                  ball.bounce = jsonBall.bounce;                  ball.size = jsonBall.size;                  ball.pocketIndex = jsonBall.pocketIndex;                  ball.points = jsonBall.points;                  ball.initPosition = jsonBall.initPosition;                  ball.id = jsonBall.id;                  balls[balls.length] = ball;                     if (ball.points > 0 && ball.pocketIndex == null) {                      ballsOnTable++;                  }              }                 //if there is no more balls on the table, clear local storage              //and reload the game              if (ballsOnTable == 0) {                  localStorage.clear();                  window.location.reload();              }                 var jsonTeams = $.evalJSON(localStorage.getItem("teams"));              teams = jsonTeams;              if (jsonTeams[0].BallOn)                  teams[0].BallOn = balls[jsonTeams[0].BallOn.id];              if (jsonTeams[1].BallOn)                  teams[1].BallOn = balls[jsonTeams[1].BallOn.id];                 playingTeamID = localStorage.getItem("playingTeamID");              awaitingTeamID = localStorage.getItem("awaitingTeamID");          }      }


毫無疑問,HTML5將完全改變web世界。這次改革正在進(jìn)行中,我希望這篇文章能邀請你一起加入這次革命,在這里我們看到了HTML5中的Canvas,CSS3,音頻和本地存儲。盡管斯諾克游戲看起來很復(fù)雜,但使用了HTML5技術(shù)后就變得非常簡單了。我從來都沒有想過居然會有這么好的效果。

關(guān)于如何用HTML 5打造斯諾克桌球俱樂部問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI