首先我声明一下,我不是原作者,实现代码仅供参考和研究,原版是用canvas画的,我这版是用DOM实现,而且只有1关作为示例。
玩原版coreball游戏请到这里—— http://coreball.sinaapp.com/
<!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表示一个小球。
不过现在显示出来的样子是这样的——
缺少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后,界面长这样——
好看一点了有木有?稍微解说一下关键的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;
}
于是界面长这样了——
之前月影有道面试题,大概是如何让一个小球以某个固定点为圆心,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;
}
这样的话,小球就可以旋转起来了。
因为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]);
}
}
接下来是实现 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旋转的角度,具体公式可以看这篇文章
接下来是完整的代码:
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;
}
有了碰撞检测,接下来我们就可以添加停止游戏的逻辑了——
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微信公众号
666 ( ̄︶ ̄)↗ 涨
又学到了新知识~(≧▽≦)/~
cool
共收到3条回复