3D魔方
一、3D魔方内容简介
3D魔方是一款益智类的游戏,是魔方游戏的一个变体,游戏采用了3D的图形界面,看起来还是非常逼真的。游戏的玩法也和一般性的魔方游戏相同,实现一个网页版的3D魔方,主要实现的功能是通过鼠标拖拽实现魔方的单层旋转和整体旋转。
二、3D立方体的创建
1、先将平面上的6个DIV拼接在一起。形成一张类似于3d立方体图形展开的平面图。如图所示:
图1
将DIV合在一起,将会用到定位的知识,将立方体包起来需要一个大盒子DIV,所以总共需要7个DIV,这七个DIV,将以大盒子为参考点(position:relative),剩下的6个子盒子,每一个都是绝对定位,定位在相应的位置上,定位完成后,就是上面的那个样子。
2、我们需要将每一个面旋转到相应的位置上,每一个面的旋转轴都是不一样的。上下,左右,分别对应的旋转轴,以及旋转角度分别是:bottom(90deg),top(-90deg),right(90deg),left(-90deg)。同时要注意在旋转后面的时候,旋转轴为Z轴,并不是上下,左右边。浏览器上面的坐标系是这样的:Z轴是屏幕里外两个方向(向外为正,向里为负),X轴的水平方向(向右为正,向左为负),Y轴的竖直方向(向下为正,向上为负)。了解完上面的知识后,就可以实现将后边的面向里移动啦,通俗来说,就是将“后”面沿着Z轴向里移动整个Div的宽度,大小,CSS3代码如下:transform:translate3d(-50%,-50%,0px)。完成以上操作后就会得到如下的效果:
图2
3、接下来还有一个关键的步骤,就是当变换导致元素在 3D 空间中旋转时,指定当元素背面朝向观察者时不可见,具体代码如下:
-webkit-backface-visibility: hidden;
-moz-backface-visibility: hidden;
-o-backface-visibility: hidden;
backface-visibility: hidden;
4、接下来我们要做的就是设置一下所处环境,我们要设置成3D的环境,具体的语法形式如下:transform-style: preserve-3d;
5、然后我们为了让立方体旋转起来,以便更好的实现3D效果。首先找到旋转中心,在3D魔方中,旋转中心就是立方体的几何中心。
三、魔方整体样式的创建
1、魔方由6个中心块、8个角块、12个棱块和1个主轴构成,共有27个正方体,其中中心块有1个颜色、角块3个颜色、棱块2个颜色
图3
2、从cubeletId-0到cubeletId-26共构建了27个正方体模块,将元素显示为块级元素,每个正方体的6个面也要进行设置,以X轴设置左右面,以Y轴设置上下面,以Z轴设置前后面,定义3D旋转。对于魔方整体,规定所有子元素都在3D空间中呈现,perspective属性定义3D元素距视图的距离,该元素允许改变3D元素查看3D元素的视图。定义3D转换,使用16个值的4*4矩阵。
3、然后给魔方加上动画过渡效果,以及旋转角度和旋转轴就可以实现流畅的旋转了。动画过渡效果是使用TweenJS实现的,TweenJS提供了一个简单但强大的渐变界面。它支持渐变的数字对象属性&CSS样式属性,并允许链接补间动画和行动结合起来,创造出复杂的序列。它主要包括的参数如图所示:
图4
四、单层魔方的旋转
可以通过修改rotation分量的值来指定魔方绕中心点以什么轴旋转,比如说rotation.x是指当前立方体需要绕中心点以X轴旋转(从坐标轴正方向朝中心点看)。现在我们只讨论魔方的其中一个立方体的旋转情况,它需要绕Z轴顺时针旋转θ度。这整个过程可以拆分成旋转和平移。其中立方体的旋转可以理解为移到中心按顺时针旋转θ度,然后再平移到目标位置。
变换过程可以用下面的公式表示,其中p为旋转前立方体的中心位置,p' 为旋转后立方体的中心位置,Rz(θ) 为绕z轴顺时针旋转θ度(即rotation.z),Tp'则是平移矩阵,v和v'分别为变换前后的立方体顶点:
p′=p×Rz(θ)p′=p×Rz(θ)
实际上它只会根据rotation来按其中一个轴旋转。现在我们尝试给魔方的顶面绕Y轴顺时针旋转,在Rublik::Update方法内部用下述代码:
void Rubik::Update(float dt) {
for (int i = 0; i < 3; ++i)
for (int k = 0; k < 3; ++k)
mCubes[i][2][k].rotation.y += XM_PI * dt; }
然后再调用这个函数,魔方的一层就能旋转起来了,如下图所示:
图5
五、鼠标拖拽魔方旋转
1、鼠标实现拖拽主要的就是有监听鼠标事件,计算鼠标滑动距离,改变魔方的rotateX、rotateY。鼠标按下时,记录鼠标当前所处位置,移动时又可以获取实时位置,用移动时获取的实时位置减去鼠标按下时的位置,就可以得到鼠标移动的相对位置。鼠标松开,可以结束计算。
* 获取鼠标实时移动的坐标;m_move_x,m_move_y
* 鼠标按下时的坐标;m_down_x,m_down_y
* div的坐标;dx,dy
* 鼠标按下时,鼠标与div的偏移量;md_x,md_y
* div的新坐标;ndx,ndy
代码实现:
//鼠标按下
function down(){
move_div = document.getElementById("move_div");
isDown = true;
//获取鼠标按下时坐标
m_down_x = event.pageX;
m_down_y = event.pageY;
//获取div坐标
dx = move_div.offsetLeft;
dy = move_div.offsetTop;
//获取鼠标与div偏移量
md_x = m_down_x - dx;
md_y = m_down_y - dy;
}
//鼠标移动
function move(){
move_div = document.getElementById("move_div");
//实时更新div的坐标
dx = move_div.offsetLeft;
dy = move_div.offsetTop;
//获取鼠标移动实时坐标
m_move_x = event.pageX;
m_move_y = event.pageY;
//鼠标按下时移动才触发
if(isDown){
//获取新div坐标,鼠标实时坐标 - 鼠标与div的偏移量
ndx = m_move_x - md_x;
ndy = m_move_y - md_y;
//把新div坐标值赋给div对象
move_div.style.left = ndx+"px";
move_div.style.top = ndy+"px";
}
}
//鼠标释放
function up(){
isDown = false;
}
2、给魔方的8个角点定位,以方便魔方旋转后的位置记录,重新设置立方体边界。
图6
图7
3、根据拖拽方向判断旋转轴
我们可以看到魔方的面有+X面,+Y面和-Z面。
在我们拾取到立方体后,我们还要根据这两个信息来确定旋转轴:
(1)当前具体是拾取到立方体的哪个面
(2)当前鼠标的拖动方向
当我们的鼠标点击到一个小立方体上时,我们怎么知道点击选中的是X.Y.Z中的哪个面呢?
Rubik::HitCube函数不仅返回了拾取到的立方体索引,还有射线击中立方体表面的最短距离。我们知道-Z面的所有顶点的z值在不产生旋转的情况下都会为-3,因此我们只需要将得到的t值带入射线方程 p=e+tdp=e+td 中,判断求得的 pp 其中的z分量是否为3,如果是,那说明当前鼠标拾取的是该立方体的-Z面。
接下来就是要讨论用鼠标拖动魔方会产生怎么样的旋转问题了。我们还需要确定当前的拖动会让哪一层魔方旋转(或者说绕什么轴旋转)。以下图为例:
图8
上图的X轴和Y轴对应的是屏幕坐标系,坐标轴的原点为鼠标刚点击时的落点,通过两条虚线,可以将鼠标的拖动方向划分为四个部分,对应魔方旋转的四种情况。其中屏幕坐标系的主+X(-X)拖动方向会使得魔方的+Y面做逆(顺)时针旋转,而屏幕坐标系的主+Y(-Y)拖动方向会使得魔方的+X面做逆(顺)时针旋转。
我们可以将这些情况进行简单归类,即当X方向的瞬时位移量比Y方向的大时,魔方的+Y面就会绕Y轴进行旋转,反之则是魔方的+X面绕X轴进行旋转。
六、魔方开场效果
我们使用一个栈来记录用户的操作,这个栈不仅可以用来记录用户操作记录,还可以用来存储打乱魔方的操作。即游戏刚开始先给这个栈塞入一堆随机操作,然后每执行一个操作就退栈一次,直到栈空时打乱操作完成,用户可以开始对魔方进行操作,同时这个栈也开始记录用户操作。
同时在开始的时候,有一个镜头住键推进的效果,这是一个简单的摄像机移动过程,包含的绕Y轴的旋转和镜头的推进。这个动画过程需要根据帧时间间隔做更新。
七、成品效果展示
成果展示链接
链接:https://pan.baidu.com/s/1esGlcR4N1aX3lTZdEy_HMg
提取码:i9g5
小组成员:许梦 将安然 王嘉慧 彭欣欣