HTML5中国

 找回密码
 立即注册

QQ登录

只需一步,快速开始

HTML5中国 首页 教程视频 实例代码 查看内容

用HTML 5打造斯诺克桌球俱乐部

2011-12-14 16:26| 发布者: admin| 查看: 15296| 评论: 2

摘要: 本文介绍了如何利用HTML5技术来打造一款非常酷的斯诺克桌球游戏,文章中详细地列出了开发的全过程,并解说了实现这个游戏的几个关键点。在文章末尾我 向大家提供了游戏的在线实例页面和源码下载链接,如果你只是想玩 ...
用HTML 5打造斯诺克桌球俱乐部1
       本文介绍了如何利用HTML5技术来打造一款非常酷的斯诺克桌球游戏,文章中详细地列出了开发的全过程,并解说了实现这个游戏的几个关键点。在文章末尾我 向大家提供了游戏的在线实例页面和源码下载链接,如果你只是想玩玩(需要使用支持HTML5的浏览器,建议使用Chrome 12, Internet Explorer 9 或者 Fire Fox 5及其以上版本),那你可以跳过正文拉到页面最底端去玩玩那个游戏或者下载源码,但我建议你好好看看实现过程,对我们学习HTML5非常有帮助。

       毫无疑问,我们已经目睹了HTML5背后的那场伟大的Web开发革命。经过那么多年HTML4的统治,一场全新的运动即将完全改变现在的Web世 界。正是他释放出来的现代化气息和丰富的用户体验,让它很快地成为了一个独特的插件运行在类似Flash和Silverlight的框架之上。

       如果你是一个非常年轻的开发者,也许你是刚刚在开始学习HTML5,所以可能你并没有注意到他有太大的变化。在任何时候,我希望这篇文章能够帮助到你,当然,也希望像我一样的老手能从中学到一些新的花样。

        你的点评对我来说非常重要,所以我很期待你的来信。当然能让我更兴奋的是当你在那个游戏画面上右击时暗暗地说一句“Hey,这居然不是Flash!也不是Silverlight!”

系统要求

想要使用本文提供的HTML5桌球应用,你必须安装下面的这些浏览器:Chrome 12, Internet Explorer 9 or Fire Fox 5

游戏规则

也许你已经知道这是一个什么样的游戏了,是的,这是“英式斯诺克”,实际上更确切的说是“简易版英式斯诺克”,因为没有实现所有的斯诺克游戏规则。 你的目标是按顺序将目标球灌入袋中,从而比其他选手得到更多的分数。轮到你的时候,你就要出杆了:根据提示,你必须先打进一个红色球得到1分,如果打进 了,你就可以继续打其他的球 - 但是这次你只能打彩色球了(也就是除红色球以外的球)。如果成功打进,你将会得到各自彩球对应的分数。然后被打进的彩球会回到球桌上,你可以继续击打其他 的红球。这样周而复始,直到你失败为止。当你把所有的红球都打完以后,球桌上就只剩下6个彩球了,你的目标是将这6个彩球按以下顺序依次打入袋中:黄(2 分)、绿(3分)、棕(4分)、蓝(5分)、粉(6分)、黑(7分)。如果一个球不是按上面顺序打进的,那它将会回到球桌上,否则,它最终会留在袋里。当 所有球都打完后,游戏结束,得分最多的人胜出。

犯规处理

为了处罚你的犯规,其他选手将会得到你的罚分:

◆ 白球掉入袋中罚4分

◆ 白球第一次击中的球是错误的话罚第一个球的分值

◆ 第一个错误的球掉入袋中罚第一个球的分值

◆ 处罚的分数至少是4

下面的这段代码展示了我是如何来计算犯规的:

  1. var strokenBallsCount = 0;  
  2. console.log('strokenBalls.length: ' + strokenBalls.length);  
  3.     for (var i = 0; i < strokenBalls.length; i++) {  
  4.         var ball = strokenBalls[i];  
  5.         //causing the cue ball to first hit a ball other than the ball on  
  6.         if (strokenBallsCount == 0) {  
  7.             if (ball.Points != teams[playingTeamID - 1].BallOn.Points) {  
  8.                 if (ball.Points == 1 || teams[playingTeamID - 1].BallOn.Points == 1 ||   
  9.                 fallenRedCount == redCount) {  
  10.                     if (teams[playingTeamID - 1].BallOn.Points < 4) {  
  11.                         teams[playingTeamID - 1].FoulList[teams[playingTeamID - 1]  
  12.                         .FoulList.length] = 4;  
  13.                         $('#gameEvents').append('  
  14. Foul 4 points :  Expected ' +  
  15.                          teams[playingTeamID - 1].BallOn.Points + ', but hit ' + ball.Points);  
  16.                     }  
  17.                     else {  
  18.                         teams[playingTeamID - 1].FoulList[teams[playingTeamID - 1]  
  19.                         .FoulList.length] = teams[playingTeamID - 1].BallOn.Points;  
  20.                         $('#gameEvents').append('  
  21. Foul ' + teams[playingTeamID - 1]  
  22.                         .BallOn.Points + ' points :  Expected ' + teams[playingTeamID - 1]  
  23.                         .BallOn.Points + ', but hit ' + ball.Points);  
  24.                     }  
  25.                     break;  
  26.                 }  
  27.             }  
  28.         }  
  29.    
  30.         strokenBallsCount++;  
  31.     }  
  32.    
  33.     //Foul: causing the cue ball to miss all object balls  
  34.     if (strokenBallsCount == 0) {  
  35.         teams[playingTeamID - 1].FoulList[teams[playingTeamID - 1].FoulList.length] = 4;  
  36.         $('#gameEvents').append('  
  37. Foul 4 points :  causing the cue ball   
  38.         to miss all object balls');  
  39.     }  
  40.    
  41.     for (var i = 0; i < pottedBalls.length; i++) {  
  42.         var ball = pottedBalls[i];  
  43.         //causing the cue ball to enter a pocket  
  44.         if (ball.Points == 0) {  
  45.             teams[playingTeamID - 1].FoulList[teams[playingTeamID - 1].FoulList.length] = 4;  
  46.             $('#gameEvents').append('  
  47. Foul 4 points :  causing the cue ball  
  48.              to enter a pocket');  
  49.         }  
  50.         else {  
  51.             //causing a ball different than the target ball to enter a pocket  
  52.             if (ball.Points != teams[playingTeamID - 1].BallOn.Points) {  
  53.                 if (ball.Points == 1 || teams[playingTeamID - 1].BallOn.Points == 1  
  54.                  || fallenRedCount == redCount) {  
  55.                     if (teams[playingTeamID - 1].BallOn.Points < 4) {  
  56.                         teams[playingTeamID - 1].FoulList[teams[playingTeamID - 1]  
  57.                         .FoulList.length] = 4;  
  58.                         $('#gameEvents').append('  
  59. Foul 4 points : '  
  60.                          + ball.Points + ' was potted, while ' + teams[playingTeamID - 1]  
  61.                          .BallOn.Points + ' was expected');  
  62.                         $('#gameEvents').append('  
  63. ball.Points: ' + ball.Points);  
  64.                         $('#gameEvents').append('  
  65. teams[playingTeamID - 1]  
  66.                         .BallOn.Points: ' + teams[playingTeamID - 1].BallOn.Points);  
  67.                         $('#gameEvents').append('  
  68. fallenRedCount: ' + fallenRedCount);  
  69.                         $('#gameEvents').append('  
  70. redCount: ' + redCount);  
  71.                     }  
  72.                     else {  
  73.                         teams[playingTeamID - 1].FoulList[teams[playingTeamID - 1]  
  74.                         .FoulList.length] = teams[playingTeamID - 1].BallOn.Points;  
  75.                         $('#gameEvents').append('  
  76. Foul ' + teams[playingTeamID - 1]  
  77.                         .BallOn.Points + ' points : ' + ball.Points + ' was potted, while '  
  78.                          + teams[playingTeamID - 1].BallOn.Points + ' was expected');  
  79.                     }  
  80.                 }  
  81.             }  
  82.         }  
  83.     } 

得分

我们根据下面的规则来计算得分:红(1分)、黄(2分)、绿(3分)、棕(4分)、蓝(5分)、粉(6分)、黑(7分)。代码如下:

  1. if (teams[playingTeamID - 1].FoulList.length == 0) {  
  2.            for (var i = 0; i < pottedBalls.length; i++) {  
  3.                var ball = pottedBalls[i];  
  4.                //legally potting reds or colors  
  5.                wonPoints += ball.Points;  
  6.                $('#gameEvents').append('  
  7. Potted +' + ball.Points + ' points.');  
  8.            }  
  9.        }  
  10.        else {  
  11.            teams[playingTeamID - 1].FoulList.sort();  
  12.            lostPoints = teams[playingTeamID - 1].FoulList[teams[playingTeamID - 1].FoulList.length - 1];  
  13.            $('#gameEvents').append('  
  14. Lost ' + lostPoints + ' points.');  
  15.        }  
  16.        teams[playingTeamID - 1].Points += wonPoints;  
  17.        teams[awaitingTeamID - 1].Points += lostPoints; 


选手的闪动动画头像


游戏是有两位选手参与的,每一位选手都有自己的昵称和头像,选手的昵称我们就简单地以“player 1”和“player 2”来命名了(也许让用户自己输入会更漂亮)。每位选手的头像是一只正在打桌球的可爱小狗。当轮到其中一位选手时,他的头像就会有一闪一闪的动画效果,同 时对手的头像会停止闪动。

这个效果我们是通过改变img元素的CSS3属性opacity的值来实现的:我们使用jquery的animatio函数让opacity的值在0-1.0之间变化。

  1. function animateCurrentPlayerImage() {  
  2.     var otherPlayerImageId = 0;  
  3.     if (playingTeamID == 1)  
  4.         otherPlayerImageId = 'player2Image';  
  5.     else  
  6.         otherPlayerImageId = 'player1Image';  
  7.     var playerImageId = 'player' + playingTeamID + 'Image';  
  8.     $('#' + playerImageId).animate({  
  9.         opacity: 1.0  
  10.     }, 500, function () {  
  11.         $('#' + playerImageId).animate({  
  12.             opacity: 0.0  
  13.         }, 500, function () {  
  14.             $('#' + playerImageId).animate({  
  15.                 opacity: 1.0  
  16.             }, 500, function () {  
  17.             });  
  18.         });  
  19.     });  
  20.    
  21.     $('#' + otherPlayerImageId).animate({  
  22.         opacity: 0.25  
  23.     }, 1500, function () {  
  24.     });  
  25. }  


力量控制条

个优秀的斯诺克选手都能很好地把握住每一杆的力度.不同的技巧需要不同的击球方式:直接的,间接的,或者利用边角的等等。不同方向和不同力度的组合可以构造成千上万种可能的路径。幸运的是,这个游戏提供了一个非常漂亮的力度控制条,可以帮助选手在击球前调整他们的球杆。

为了达到这一点,我们使用了HTML5的meter元素标签,它可以完成测量距离的工作。meter标签最好在知道这次测量的最小值和最大值的情况 下使用。在我们的这个例子中,这个值在0到100之间,因为IE9不支持meter,所以我用了一张背景图来替代,这样效果也是一样的。

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

当你点击了力度条后,你实际上是选择了一个新的力度。一开始你可能不是很熟练,但在真实世界中,这是需要时间来训练自己的能力的。点击力度条的代码如下:

  1. $('#strengthBar').click(function (e) {  
  2.     var left = $('#strengthBar').css('margin-left').replace('px', '');  
  3.     var x = e.pageX - left;  
  4.     strength = (x / 150.0);  
  5.     $('#strengthBar').val(strength * 100);  
  6. });  


在当前选手的头像框里面,你会注意到有一个小球,我叫他“ball on”,就是当前选手在规定时间内应该要击打的那个球。如果这个球消失了,那选手将失去4分。同样如果选手第一次击中的球不是框内显示的球,那他也将失去4分。

这个“ball on”是直接将canvas元素覆盖在用户头像上的,所以你在头像上看到的那个球,他看起来像是在标准的div上盖了一个img元素,但是这个球并不是 img实现的。当然我们也不能直接在div上画圆弧和直线,这就是为什么我要将canvas覆盖到头像上的原因了。看看代码吧:

  1. <canvas id="player1BallOn" class="player1BallOn">   
  2. canvas>     <canvas id="player2BallOn" class="player2BallOn">   
  3. canvas> 


  1. var player1BallOnContext = player1BallOnCanvas.getContext('2d');  
  2. var player2BallOnContext = player2BallOnCanvas.getContext('2d');  
  3. .  
  4. .  
  5. .  
  6. function renderBallOn() {  
  7.     player1BallOnContext.clearRect(0, 0, 500, 500);  
  8.     player2BallOnContext.clearRect(0, 0, 500, 500);  
  9.     if (playingTeamID == 1) {  
  10.         if (teams[0].BallOn != null)  
  11.             drawBall(player1BallOnContext, teams[0].BallOn, new Vector2D(30, 120), 20);  
  12.     }  
  13.     else {  
  14.         if (teams[1].BallOn != null)  
  15.             drawBall(player2BallOnContext, teams[1].BallOn, new Vector2D(30, 120), 20);  
  16.         player1BallOnContext.clearRect(0, 0, 133, 70);  
  17.     }  


旋转屋顶上的电风扇

在这个游戏中这把电风扇纯属拿来玩玩有趣一把的。那为什么这里要放一把电风扇?是这样的,这个游戏的名字叫HTML5斯诺克俱乐部,放一把电风扇就有俱乐部的气氛了,当然,我也是为了说明如何实现CSS3的旋转。

实现这个非常简单:首先我们需要一张PNG格式的电扇图片。只是我们并没有用电扇本身的图片,我们用他的投影。通过显示风扇在球桌上的投影,让我们觉得它在屋顶上旋转,这样就达到了我们目的:

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


为了获得更为逼真的气氛,我用Paint.Net软件将电扇图片平滑化了,现在你再也看不到电扇的边缘了。我觉得这是达到如此酷的效果最为简单的办法。

除了用了这图像处理的把戏,我们仅仅使用了一个带背景图的普通的div元素,这并没有什么特别。既然我们已经得到了电扇图片,我们就要让它开始旋转了。这里我们使用CSS3的rotate属性来实现这一切。

球杆动画


球杆的动画对于这个游戏也不是必需的,但是这的确为此添加了不少乐趣。当你开始用鼠标在球桌上移动时,你会注意到球杆的确是跟着你的鼠标在转动。这 就是说球杆会一直保持跟随鼠标的移动,就像你身临其境一般真实。因为选手只能用他的眼睛来瞄准,所以这个效果也会对选手有所帮助。

球杆是单独一张PNG图片,图片本身不直接以img的形式展现,也不以背景的形式展现,相反,它是直接展现在一个专门的canvas上的。当然我们也可以用div和css3来达到同样的效果,但我觉得这样能更好的说明如何在canvas上展现图片。

首先,canvas元素会占据几乎整个页面的宽度。请注意这个特别的canvas有一个很大的z-index值,这样球杆就可以一直在每个球的上方 而不会被球遮盖。当你在球桌上移动鼠标时,目标点会实时更新,这时候球杆图片会进行2次转换:首先,通过计算得到母球的位置,其次翻转母球周围的球杆,通 过这2步我们就得到了鼠标所在点和母球的中心点。

  1. #cue { position:absolute; }  
  2. .  
  3. .  
  4. .  
  5. if (drawingtopCanvas.getContext) {  
  6.     var cueContext = drawingtopCanvas.getContext('2d');  
  7. }  
  8. .  
  9. .  
  10. .  
  11. var cueCenter = [15, -4];  
  12. var cue = new Image;  
  13. cue.src = '<%: Url.Content("../Content/Images/cue.PNG") %>';  
  14.    
  15. var shadowCue = new Image;  
  16. shadowCue.src = '<%: Url.Content("../Content/Images/shadowCue.PNG") %>';  
  17. cueContext.clearRect(0, 0, topCanvasWidth, topCanvasHeight);  
  18.    
  19.     if (isReady) {  
  20.         cueContext.save();  
  21.         cueContext.translate(cueBall.position.x + 351, cueBall.position.y + 145);  
  22.         cueContext.rotate(shadowRotationAngle - Math.PI / 2);  
  23.         cueContext.drawImage(shadowCue, cueCenter[0] + cueDistance, cueCenter[1]);  
  24.         cueContext.restore();  
  25.         cueContext.save();  
  26.         cueContext.translate(cueBall.position.x + 351, cueBall.position.y + 140);  
  27.         cueContext.rotate(angle - Math.PI / 2);  
  28.         cueContext.drawImage(cue, cueCenter[0] + cueDistance, cueCenter[1]);  
  29.         cueContext.restore();  
  30.     } 

为了让球杆变得更真实我们为球杆添加了投影,并且我们故意让球杆投影的旋转角度和球杆的角度不一样,我们这样做是为了让球杆有3D的效果。最终的效果实在是太酷了。

123下一页
更多
3

鲜花

握手

雷人

路过

鸡蛋

刚表态过的朋友 (3 人)

相关阅读

发表评论

最新评论

引用 童炎 2012-8-6 16:44
好强悍。。。
引用 ren 2012-2-9 22:25
非常好

查看全部评论(2)

HTML5中国微信

小黑屋|关于我们|HTML5论坛|友情链接|手机版|HTML5中国 ( 京ICP备11006447号 京公网安备:11010802018489号  

GMT+8, 2017-6-25 11:26

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

返回顶部