解析小球之间的碰撞

上图来自维基百科
我觉得解析小球间的碰撞问题是这个项目的核心,首先我们需要比较2个小球的组合(ball 1和ball 2)。然后我们找到一个“碰撞口”,也就是在碰撞的那一刻将它们移动到准确的位置。要完成这些我们需要做一些矢量运算。下一步就是要计算最终碰撞的冲力,最后就是要改变两个小球的冲量,也就是用它的冲力去加上或减去其速度向量得到的结果。当碰撞结束后,它们的位置和速度都将发生变化。
广州网站建设,网站建设,广州网页设计,广州网站设计
- 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));
- }
检测小球与转角间的碰撞
咋眼看,要检测小球与转角之间的碰撞似乎有点复杂,但幸运的是有一个非常简单却有效的方法来解决这个问题:由于转角也是圆形元素,我们可以把它们想象成固定的小球,如果我们能正确的确定固定小球的大小和位置,那么我们就像处理小球之间的碰撞那样解决小球和转角的碰撞问题。事实上,我们可以用同一个函数来完成这件事情,唯一的区别是这些转角是固定不动的。
下图是假设转角都是一些小球,那就会这样子:

分析小球与转角之间的碰撞
如上面说到的那样,小球之间的碰撞和小球与转角的碰撞唯一不同的是后者我们要确保他保持固定不动,代码如下:
- 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));
- }
检测小球与矩形边缘的碰撞
我们通过小球与矩形边缘的碰撞检测来知道小球是否到达了球桌的上下左右边缘。检测的方式非常简单:每个小球需要检测4个点:我们通过对小球的x、y坐标的加减来计算出这些点。然后将它们和我们定义的球桌矩形范围进行对比,看它们是否在这个范围内。
广州网站建设,网站建设,广州网页设计,广州网站设计
分析小球与矩形边缘的碰撞

上图来自维基百科
处理小球与矩形边缘的碰撞比处理小球之间的碰撞简单很多。我们需要在矩形边界上找到离小球中心点最近的点,如果这个点在小球的半径范围内,那就说明碰撞了。
播放音频
没有一个游戏是没有声音的,不同的平台处理音频的方式不同。幸运的是HTML5给我们提供了一个audio标签,这简化了我们定义音频文件,加载音频和调节音量的工作。
一般的HTML5例子都是给大家看audio的标准用法,就是展现一个播放控制条。在这个游戏中,我们使用了不同的方法,并隐藏了音频播放控制条。这样做是有道理的,因为音频的播放不是直接由用户控制的,而是由游戏中的事件触发的。
页面上一共有8个audio标签,其中6个小球碰撞的声音,一个是击打的声音,一个则是小球掉入袋中的声音。这些声音可以同时播放,所以我们不用考虑并发的情况。
当选手射击母球时,我们就根据用户选择的力度来播放对应音量的击球声音频。
- $('#topCanvas').click(function (e) {
- .
- .
- .
- audioShot.volume = strength / 100.0;
- audioShot.play();
- .
- .
- .
- });
当一个小球碰到了另一个小球,我们就计算出碰撞的强度,然后选择合适音量的audio标签播放。
- 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();
- }
- .
- .
- .
- }
最后,当小球掉入袋中,我们就播放“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();
- }
- }
- }
- }
- }
本地存储游戏状态
有时候我们叫它web存储或者DOM存储,本地存储HTML5定义的一种机制,用来保持本地数据。文章开头提到的那几种浏览器原生就支持本地存储,所以我们不需要使用额外的js框架。
我们使用本地存储主要用来保存用户的游戏状态。简而言之,我们是要允许用户在开始游戏一段时间后,关闭浏览器,第二天打开还能继续往下玩。
当游戏开始后,我们需要检索在本地是否有数据存储着,有的话就加载它们:
- jQuery(document).ready(function () {
- ...
- retrieveGameState();
- ...
另一方面,游戏开始后我们需要对每一次射击的数据进行保存。
- function render() {
- ...
- processFallenBalls();
- saveGameState();
- ...
- }
本地存储是由一个字符串字典实现的。这个简单的结构体接受传入的字符串和数字。我们只需要用setItem来将数据存储到本地。下面的代码说明了我们是如存储时间数据,小球位置坐标数据,选手数据和当前击球选手与等待击球选手的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);
- }
- }
我觉得除了下面的部分,上面的代码都已经解释了自己的作用了:
- localStorage.setItem("balls", $.toJSON(balls));
- localStorage.setItem("teams", $.toJSON(teams));
目前为止,本地存储还不能工作,我们需要将它们字符化,上面的2行代码是利用了jquery的toJSON方法将复杂的对象转换成了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},........
一旦我们将这些对象序列化到本地存储后,我们就可以用类似的方法将它们检索出来,我们现在就是用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世界。这次改革正在进行中,我希望这篇文章能邀请你一起加入这次革命,在这里我们看到了HTML5中的Canvas,CSS3,音频和本地存储。尽管斯诺克游戏看起来很复杂,但使用了HTML5技术后就变得非常简单了。我从来都没有想过居然会有这么好的效果。



