我是怎样实现Coreball游戏的

我是怎样实现Coreball游戏的

首先我声明一下,我不是原作者,实现代码仅供参考和研究,原版是用canvas画的,我这版是用DOM实现,而且只有1关作为示例。

玩原版coreball游戏请到这里—— http://coreball.sinaapp.com/


HTML结构: 旋转的列表

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Coreball</title>
</head>
<body>
 <div>
 <ul id="levelballs">
 <li></li>
 <li></li>
 <li></li>
 <li></li>
 </ul>
 <ul id="addedballs">
 <li>8</li>
 <li>7</li>
 <li>6</li>
 <li>5</li>
 <li>4</li>
 <li>3</li>
 <li>2</li>
 <li>1</li>
 </ul>
 </div>
</body>
</html>

结构中采用两个ul来分别表示旋转的"风车",以及要添加的一溜小球,每个li表示一个小球。

不过现在显示出来的样子是这样的——

img

缺少css,裸奔果然丑爆了有木有?

啥也别说了,赶紧加css呗——

html, body{
 height: 100%;
 overflow: hidden;
 background: black;
 -webkit-user-select: none;
}
/*大球的通用样式*/
ul {
 background: white;
 color: black;
 list-style-type:none;
 padding: 0;
}
/*小球的通用样式*/
ul >li {
 width: 40px;
 height: 40px;
 border-radius:50%;
 background: white;
 line-height: 40px;
 font-size: 1.5rem;
 text-align: center;
}
/*大球的样式*/
#levelballs {
 width: 80px;
 height: 80px;
 margin: 220px auto;
 border-radius:50%;
 -webkit-transform: rotate(45deg);
}
/*大球周围的小球*/
#levelballs >li {
 position: absolute;
 float: left;
 margin: 20px 0 0 240px;
 -webkit-transform-origin: -200px 20px;
}
#levelballs >li:nth-child(1){
 -webkit-transform: rotate(0deg);
}
#levelballs >li:nth-child(2){
 -webkit-transform: rotate(90deg);
}
#levelballs >li:nth-child(3){
 -webkit-transform: rotate(180deg);
}
#levelballs >li:nth-child(4){
 -webkit-transform: rotate(270deg);
}
/*玩家添加的小球*/
#addedballs {
 width: 40px;
 margin: 0 auto;
 background: transparent;
}
#addedballs >li {
 margin-bottom: 10px;
}

加了上面一坨CSS后,界面长这样——

img

好看一点了有木有?稍微解说一下关键的CSS:

白色圆球:

border-radius:50%;
background: white;

小球以大球圆心围绕大球旋转:

position: absolute;
float: left;
margin: 20px 0 0 240px;
-webkit-transform-origin: -200px 20px;

四颗小球的旋转位置

#levelballs >li:nth-child(1){
 -webkit-transform: rotate(0deg);
}
#levelballs >li:nth-child(2){
 -webkit-transform: rotate(90deg);
}
#levelballs >li:nth-child(3){
 -webkit-transform: rotate(180deg);
}
#levelballs >li:nth-child(4){
 -webkit-transform: rotate(270deg);
}

这样,我们就得到了小球和大球,但是小球和大球之间需要有一根连线,这根连线可以用:before伪元素来实现:

/*小球与大球中间的连线*/
#levelballs >li:before {
 content: "";
 float: left;
 display: block;
 width: 160px;
 height: 1px;
 margin: 20px 0 0 -160px;
 background: white;
}

于是界面长这样了——

img

小球的运动:一二三,转!

之前月影有道面试题,大概是如何让一个小球以某个固定点为圆心,R为半径旋转,好像很少有人回答出满意的答案,但其实这个问题有很多答案,最简单的方式是用css3动画:

@-webkit-keyframes rotate{
 from {-webkit-transform:rotate(0deg);}
 to {-webkit-transform:rotate(360deg);}
}
#levelballs.play {
 -webkit-animation-name: rotate;
 -webkit-animation-duration: 5.0s;
 -webkit-animation-iteration-count: infinite;
 -webkit-animation-timing-function: linear; 
}

这样的话,小球就可以旋转起来了。

JavaScript实现的交互

因为levelballs通过css动画实现旋转,需要获取它的旋转角度,小球从正下方添加到levelballs中时,它的转角与levelballs的转角的关系为小球的转角等于90度减去levelballs的转角——

function appendBall(){
 if(balls.length){
 var deg = 90 - getRotationOf(levelballs);
 balls[0].style.webkitTransform = 'rotate(' + deg + 'deg)';
 levelballs.appendChild(balls[0]);
 }
}

img

接下来是实现 getRotationOf(levelballs),这是一个数学问题:

function getRotationOf(el){
 var style = window.getComputedStyle(el);
 if(style){
 var prop = style.getPropertyValue("-webkit-transform") || style.getPropertyValue('transform');
 prop = /matrix\((.*)\)/g.exec(prop)[1].split(',');
 prop = Math.round(Math.atan2(prop[1], prop[0]) * (180/Math.PI));
 return prop;
 }
}

基本原理并不复杂,拿到小球旋转的transform变换矩阵,再通过矩阵算出此时levelballs旋转的角度,具体公式可以看这篇文章

img

接下来是完整的代码:

void function(){'use strict'
 function getRotationOf(el){
 var style = window.getComputedStyle(el);
 if(style){
 var prop = style.getPropertyValue("-webkit-transform") || style.getPropertyValue('transform');
 prop = /matrix\((.*)\)/g.exec(prop)[1].split(',');
 prop = Math.round(Math.atan2(prop[1], prop[0]) * (180/Math.PI));
 return prop;
 }
 }
 function appendBall(){
 var balls = document.getElementById("addedballs").getElementsByTagName('li');
 if(balls.length){
 var deg = 90 - getRotationOf(levelballs);
 if(deg > 180) deg -= 360; //角度从 -180 ~ 180
 balls[0].style.webkitTransform = 'rotate(' + deg + 'deg)';
 levelballs.appendChild(balls[0]);
 }
 }
 document.documentElement.addEventListener('touchstart', appendBall);
 document.documentElement.addEventListener('mousedown', appendBall);
}();

现在我们已经可以把小球给添加到大球上了,实际的操作非常简单,就是将小球(li)从addedballs列表中取出来,然后append到levelballs列表上去。

碰撞检测

接下来我们要做碰撞检测了,小球添加上去的时候不能碰到其他的小球,这也是一个比较简单的数学问题——

//碰撞检测 
function testBalls(deg){
 var balls = document.getElementById("levelballs").getElementsByTagName('li');
 for(var i = 0; i < balls.length; i++){
 var d = getRotationOf(balls[i]);
 if(Math.abs(deg - d) <= 10 || 
 Math.abs(deg + 360 - d) <= 10){
 return false;
 }
 }
 return true;
}

因为小球的半径r是20px,小球圆心与大球圆心的距离R是160+20+40=220px,避免碰撞的近似公式满足 2r >= d * R,所以 d >= 2r / R 大约是 0.182 单位是弧度,转换成角度是 0.182 * 180 / 3.14 = 10.4,所以碰撞的标准就是角度相差小于等于10度,因为角度是周期的,所以做如下判断——

if(Math.abs(deg - d) <= 10 || 
 Math.abs(deg + 360 - d) <= 10){
 return false;
}

img

结束游戏

有了碰撞检测,接下来我们就可以添加停止游戏的逻辑了——

function gameOver(state){
 setTimeout(function(){
 //这里延迟停止动画,不然的话appendChild异步插入,会出问题
 levelballs.className += ' gameover';
 });
 document.body.className = state;
 document.documentElement.removeEventListener('touchstart', appendBall);
 document.documentElement.removeEventListener('mousedown', appendBall);
 document.body.addEventListener('touchstart', location.reload.bind(location));
 document.body.addEventListener('mousedown', location.reload.bind(location)); 
}

游戏结束时,注销事件,并通过className改变css控制展现效果。改变appendBall方法,增加——

function appendBall(){
 var balls = document.getElementById("addedballs").getElementsByTagName('li');
 if(balls.length){
 var deg = 90 - getRotationOf(levelballs);
 if(deg > 180) deg -= 360; //角度从 -180 ~ 180
 var pass = testBalls(deg);
 balls[0].style.webkitTransform = 'rotate(' + deg + 'deg)';
 levelballs.appendChild(balls[0]);
 if(pass){
 //成功插入
 if(balls.length <= 0){ //所有的小球都插入了
 gameOver("win");
 }
 }else{
 //失败
 gameOver("lose");
 }
 }
}

修改css,添加——

#levelballs.gameover {
 -webkit-animation-play-state:paused; 
}
body.win, body.lose {
 -webkit-user-select: none;
 -webkit-transition-property: background-color;
 -webkit-transition-duration: 0.7s;
 -webkit-transition-timing-function: ease-in-out;
}
body.win {
 background: green;
}
body.lose {
 background: red;
}

最后一个小问题,因为在PC上开发的,发现在手机上显示太大了,改一下meta头——

<meta name="viewport" content="width=device-width, initial-scale=0.6, maximum-scale=0.6, user-scalable=0" />

这样,整个简单的coreball游戏主体功能就完成啦~

完整代码和在线演示在这里

欢迎讨论,谢谢大家~

w3ctech微信

扫码关注w3ctech微信公众号

共收到3条回复

  • 666 ( ̄) ̄)↗ 涨

    回复此楼
  • 又学到了新知识~(≧▽≦)/~

    回复此楼
  • cool

    回复此楼

AltStyle によって変換されたページ (->オリジナル) /