同时, 还有transform
属性来对3D的节点进行平移、缩放、旋转以及拉伸.
属性值很简单, 在我们平时的web开发中也很少用到.
那用这些CSS3D属性可以做3D游戏吗?
当然是可以的.
即使只有沙盒, 也有我的世界这种神作.
今天我就来带大家玩一个从未有过的全新3D体验.
废话不多说, 我们先来看下效果:
这里是试玩地址pc端畅玩[1]
我们要完成这个迷宫大作战,需要完成以下步骤:
-
创建一个3D世界
-
写一个3D相机的功能
-
创建一座3D迷宫
-
创建一个可以自由运动的玩家
-
在迷宫中找出一条最短路径提示
我们先来看下一些前置知识.
做一款CSS3D游戏需要的知识和概念
==================
CSS3D坐标系
在css3D中, 首先要明确一个概念, 3D坐标系.
使用左手坐标系, 伸出我们的左手, 大拇指和食指成L状, 其他手指与食指垂直, 如图:
WechatIMG315.png
大拇指为X轴, 食指为Y轴, 其他手指为Z轴.
这个就是CSS3D中的坐标系.
透视属性
perspective
为css中的透视属性.
这个属性是什么意思呢, 可以把我们的眼睛看作观察点, 眼睛到目标物体的距离就是视距, 也就是这里说的透视属性.
大家都知道, 「透视」+「2D」= 「3D」.
perspective: 1200px;
-webkit-perspective: 1200px;
复制代码
3D相机
在3D游戏开发中, 会有相机的概念, 即是人眼所见皆是相机所见.
在游戏中场景的移动, 大部分都是移动相机.
例如赛车游戏中, 相机就是跟随车子移动, 所以我们才能看到一路的风景.
在这里, 我们会使用CSS去实现一个伪3d相机.
变换属性
在CSS3D中我们对3D盒子做平移、旋转、拉伸、缩放使用transform
属性.
-
translateX 平移X轴
-
translateY 平移Y轴
-
translateZ 平移Z轴
-
rotateX 旋转X轴
-
rotateY 旋转Y轴
-
rotateZ 旋转Z轴
-
rotate3d(x,y,z,deg) 旋转X、Y、Z轴多少度
注意:
这里「先平移再旋转」和「先旋转再平移」是不一样的
旋转的角度都是角度值.
这里还有不清楚的同学可以参阅羽飞的这篇[juejin.cn/post/699769…[2]] 附带有demo
矩阵变换
我们完成游戏的过程中会用到矩阵变换.
在js中, 获取某个节点的transform
属性, 会得到一个矩阵, 这里我打印一下, 他就是长这个样子:
var _ground = document.getElementsByClassName(“ground”)[0];
var bg_style = document.defaultView.getComputedStyle(_ground, null).transform;
console.log(“矩阵变换---->>>”,bg_style)
复制代码
WechatIMG307.png
那么我们如何使用矩阵去操作transform呢?
在线性变换中, 我们都会去使用矩阵的相乘.
CSS3D中使用4*4的矩阵进行3D变换.
下面的矩阵我均用二维数组表示.
例如matrix3d(1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,1)
可以用二维数组表示:
[
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
]
复制代码
平移即使使用原来状态的矩阵和以下矩阵相乘, dx, dy, dz分别是移动的方向x, y, z.
[
[1, 0, 0, dx],
[0, 1, 0, dy],
[0, 0, 1, dz],
[0, 0, 0, 1]
]
复制代码
绕X轴旋转???, 即是与以下矩阵相乘.
[
[1, 0, 0, 0],
[0, cos???, sin???, 0],
[0, -sin???, cos???, 0],
[0, 0, 0, 1]
]
复制代码
绕Y轴旋转???, 即是与以下矩阵相乘.
[
[cos???, 0, -sin???, 0],
[0, 1, 0, 0],
[sin???, 0, cos???, 0],
[0, 0, 0, 1]
]
复制代码
绕Z轴旋转???, 即是与以下矩阵相乘.
[
[cos???, sin???, 0, 0],
[-sin???, cos???, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
]
复制代码
具体的矩阵的其他知识这里讲了, 大家有兴趣可以自行下去学习.
我们这里只需要很简单的旋转应用.
开始创建一个3D世界
==========
我们先来创建UI界面.
-
相机div
-
地平线div
-
棋盘div
-
玩家div(这里是一个正方体)
注意
正方体先旋转在平移, 这种方法应该是最简单的.
一个平面绕X轴、Y轴旋转180度、±90度, 都只需要平移Z轴.
这里大家试过就明白了.
我们先来看下html部分:
复制代码
很简单的布局, 其中linex
、liney
、linez
是我画的坐标轴辅助线.
红线为X轴, 绿线为Y轴, 蓝线为Z轴. 接着我们来看下正方体的主要CSS代码.
…
.box-con{
width: 50px;
height: 50px;
transform-style: preserve-3d;
transform-origin: 50% 50%;
transform: translateZ(25px) ;
transition: all 2s cubic-bezier(0.075, 0.82, 0.165, 1);
}
.wall{
width: 100%;
height: 100%;
border: 1px solid #fdd894;
background-color: #fb7922;
}
.wall:nth-child(1) {
transform: translateZ(25px);
}
.wall:nth-child(2) {
transform: rotateX(180deg) translateZ(25px);
}
.wall:nth-child(3) {
transform: rotateX(90deg) translateZ(25px);
}
.wall:nth-child(4) {
transform: rotateX(-90deg) translateZ(25px);
}
.wall:nth-child(5) {
transform: rotateY(90deg) translateZ(25px);
}
.wall:nth-child(6) {
transform: rotateY(-90deg) translateZ(25px);
}
复制代码
粘贴一大堆CSS代码显得很蠢.
其他CSS这里就不粘贴了, 有兴趣的同学可以直接下载源码查看. 界面搭建完成如图所示:
WechatIMG308.png
接下来就是重头戏了, 我们去写js代码来继续完成我们的游戏.
完成一个3D相机功能
==========
相机在3D开发中必不可少, 使用相机功能不仅能查看3D世界模型, 同时也能实现很多实时的炫酷功能.
一个3d相机需要哪些功能?
最简单的, 上下左右能够360度无死角观察地图.同时需要拉近拉远视距.
通过鼠标交互
鼠标左右移动可以旋转查看地图; 鼠标上下移动可以观察上下地图; 鼠标滚轮可以拉近拉远视距.
✅ 1. 监听鼠标事件
首先, 我们需要通过监听鼠标事件来记录鼠标位置, 从而判断相机上下左右查看.
/** 鼠标上次位置 */
var lastX = 0, lastY = 0;
/** 控制一次滑动 */
var isDown = false;
/** 监听鼠标按下 */
document.addEventListener(“mousedown”, (e) => {
lastX = e.clientX;
lastY = e.clientY;
isDown = true;
});
/** 监听鼠标移动 */
document.addEventListener(“mousemove”, (e) => {
if (!isDown) return;
let _offsetX = e.clientX - lastX;
let _offsetY = e.clientY - lastY;
lastX = e.clientX;
lastY = e.clientY;
//判断方向
var dirH = 1, dirV = 1;
if (_offsetX < 0) {
dirH = -1;
}
if (_offsetY > 0) {
dirV = -1;
}
});
document.addEventListener(“mouseup”, (e) => {
isDown = false;
});
复制代码
✅ 2. 判断相机上下左右
使用perspective-origin
来设置相机的上下视线.
使用transform
来旋转Z轴查看左右方向上的360度.
/** 监听鼠标移动 */
document.addEventListener(“mousemove”, (e) => {
if (!isDown) return;
let _offsetX = e.clientX - lastX;
let _offsetY = e.clientY - lastY;
lastX = e.clientX;
lastY = e.clientY;
var bg_style = document.defaultView.getComputedStyle(_ground, null).transform;
var camera_style = document.defaultView.getComputedStyle(_camera, null).perspectiveOrigin;
var matrix4 = new Matrix4();
var _cy = +camera_style.split(’ ')[1].split(‘px’)[0];
var str = bg_style.split(“matrix3d(”)[1].split(“)”)[0].split(“,”);
var oldMartrix4 = str.map((item) => +item);
var dirH = 1, dirV = 1;
if (_offsetX < 0) {
dirH = -1;
}
if (_offsetY > 0) {
dirV = -1;
}
//每次移动旋转角度
var angleZ = 2 * dirH;
var newMartri4 = matrix4.set(Math.cos(angleZ * Math.PI / 180), -Math.sin(angleZ * Math.PI / 180), 0, 0, Math.sin(angleZ * Math.PI / 180), Math.cos(angleZ * Math.PI / 180), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
var new_mar = null;
if (Math.abs(_offsetX) > Math.abs(_offsetY)) {
new_mar = matrix4.multiplyMatrices(oldMartrix4, newMartri4);
} else {
_camera.style.perspectiveOrigin = 500px ${_cy + 10 * dirV}px
;
}
new_mar && (_ground.style.transform = matrix3d(${new_mar.join(',')})
);
});
复制代码
这里使用了矩阵的方法来旋转Z轴, 矩阵类Matrix4
是我临时写的一个方法类, 就俩方法, 一个设置二维数组matrix4.set
, 一个矩阵相乘matrix4.multiplyMatrices
.
文末的源码地址中有, 这里就不再赘述了.
✅ 3. 监听滚轮拉近拉远距离
这里就是根据perspective
来设置视距.
//监听滚轮
document.addEventListener(‘mousewheel’, (e) => {
var per = document.defaultView.getComputedStyle(_camera, null).perspective;
let newper = (+per.split(“px”)[0] + Math.floor(e.deltaY / 10)) + “px”;
_camera.style.perspective = newper
}, false);
复制代码
注意:
perspective-origin属性只有X、Y两个值, 做不到和u3D一样的相机.
我这里取巧使用了对地平线的旋转, 从而达到一样的效果.
滚轮拉近拉远视距有点别扭, 和3D引擎区别还是很大.
完成之后可以看到如下的场景, 已经可以随时观察我们的地图了.
index1.gif
这样子, 一个3D相机就完成, 大家有兴趣的可以自己下去写一下, 还是很有意思的.
绘制迷宫棋盘
======
绘制格子地图最简单了, 我这里使用一个15*15的数组.
「0」代表可以通过的路, 「1」代表障碍物.
var grid = [
0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1,
0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0,
1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0,
0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1,
0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0,
1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0
];
复制代码
然后我们去遍历这个数组, 得到地图.
写一个方法去创建地图格子, 同时返回格子数组和节点数组.
这里的block
是在html中创建的一个预制体, 他是一个正方体.
然后通过克隆节点的方式添加进棋盘中.
/** 棋盘 */
function pan() {
const con = document.getElementsByClassName(“pan”)[0];
const block = document.getElementsByClassName(“block”)[0];
let elArr = [];
grid.forEach((item, index) => {
let r = Math.floor(index / 15);
let c = index % 15;
const gezi = document.createElement(“div”);
gezi.classList = “pan-item”
// gezi.innerHTML = ${r},${c}
con.appendChild(gezi);
var newBlock = block.cloneNode(true);
//障碍物
if (item == 1) {
gezi.appendChild(newBlock);
blockArr.push(c + “-” + r);
}
elArr.push(gezi);
});
const panArr = arrTrans(15, grid);
return { elArr, panArr };
}
const panData = pan();
复制代码
可以看到, 我们的界面已经变成了这样.
WechatIMG310.png
接下来, 我们需要去控制玩家移动了.
控制玩家移动
======
通过上下左右w s a d
键来控制玩家移动.
使用transform
来移动和旋转玩家盒子.
✅ 监听键盘事件
通过监听键盘事件onkeydown
来判断key
值的上下左右.
document.onkeydown = function (e) {
/** 移动物体 */
move(e.key);
}
复制代码
✅ 进行位移
在位移中, 使用translate
来平移, Z轴始终正对我们的相机, 所以我们只需要移动X轴和Y轴.
声明一个变量记录当前位置.
同时需要记录上次变换的transform
的值, 这里我们就不继续矩阵变换了.
/** 当前位置 */
var position = { x: 0, y: 0 };
/** 记录上次变换值 */
var lastTransform = {
translateX: ‘0px’,
translateY: ‘0px’,
translateZ: ‘25px’,
rotateX: ‘0deg’,
rotateY: ‘0deg’,
rotateZ: ‘0deg’
};
复制代码
每一个格子都可以看成是二维数组的下标构成, 每次我们移动一个格子的距离.
switch (key) {
case ‘w’:
position.y++;
lastTransform.translateY = position.y * 50 + ‘px’;
break;
case ‘s’:
position.y–;
lastTransform.translateY = position.y * 50 + ‘px’;
break;
case ‘a’:
position.x++;
lastTransform.translateX = position.x * 50 + ‘px’;
break;
case ‘d’:
position.x–;
lastTransform.translateX = position.x * 50 + ‘px’;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
![](https://i-blog.csdnimg.cn/blog_migrate/61bc6fa3817ac98bdf647cd86ce640c1.jpeg)
最后
资料过多,篇幅有限,需要文中全部资料可以点击这里获取前端面试资料PDF完整版!
自古成功在尝试。不尝试永远都不会成功。勇敢的尝试是成功的一半。
position.y–;
lastTransform.translateY = position.y * 50 + ‘px’;
break;
case ‘a’:
position.x++;
lastTransform.translateX = position.x * 50 + ‘px’;
break;
case ‘d’:
position.x–;
lastTransform.translateX = position.x * 50 + ‘px’;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-fPehV7FB-1713182508690)]
[外链图片转存中…(img-YT178aEA-1713182508691)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
[外链图片转存中…(img-OjJ7goBm-1713182508691)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
![](https://i-blog.csdnimg.cn/blog_migrate/61bc6fa3817ac98bdf647cd86ce640c1.jpeg)
最后
[外链图片转存中…(img-2IojqaG5-1713182508691)]
[外链图片转存中…(img-ANhIrplL-1713182508692)]
资料过多,篇幅有限,需要文中全部资料可以点击这里获取前端面试资料PDF完整版!
自古成功在尝试。不尝试永远都不会成功。勇敢的尝试是成功的一半。