360最牛前端团队-奇舞团内部分享资料!月影大神:我是怎样实现Coreball游戏的

Posted 360校园

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了360最牛前端团队-奇舞团内部分享资料!月影大神:我是怎样实现Coreball游戏的相关的知识,希望对你有一定的参考价值。

欢乐有爱的前端团队
本周奇舞团又有新分享喽~,小招姐最近经常混迹于奇舞团内部,几个leader还张罗让我加入他们的骑行团队,五一一起骑自行车郊游~我当时就在想“这真的是搞技术的一帮人吗?!”
这不,最近月影玩起了游戏,玩开心了又开始研究咋实现这个游戏(我也是醉了),关键是他还要分享给大家这门关于数学知识的技术(我已经不醒人世了)。不多说了,看看下面的文章就知道喽,纯干货分享~包教包会。


月影在给大家讲数学题

文/月影

我是怎样实现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表示一个小球。

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

360最牛前端团队-奇舞团内部分享资料!月影大神:我是怎样实现Coreball游戏的

缺少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后,界面长这样——

360最牛前端团队-奇舞团内部分享资料!月影大神:我是怎样实现Coreball游戏的

好看一点了有木有?稍微解说一下关键的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;}

于是界面长这样了——

360最牛前端团队-奇舞团内部分享资料!月影大神:我是怎样实现Coreball游戏的

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

之前月影有道面试题,大概是如何让一个小球以某个固定点为圆心,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]); }}

360最牛前端团队-奇舞团内部分享资料!月影大神:我是怎样实现Coreball游戏的

接下来是实现 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游戏主体功能就完成啦~

完整代码和在线演示在阅读原文”里



世界辣么大
快来360前端星计划看看
http://campus.360.cn


以上是关于360最牛前端团队-奇舞团内部分享资料!月影大神:我是怎样实现Coreball游戏的的主要内容,如果未能解决你的问题,请参考以下文章

月影谈为什么我们要用函数式编程

奇虎360前端团队奇舞团负责人推荐阅读:在2017年将会更加流行的6个Web开发趋势

第1779期使用TypeScript两年后,值得吗?

全栈 指北

CSS字体:字体特性

Async Clipboard AP