当前位置:   article > 正文

前端游戏巨制! CSS居然可以做3D游戏了,意外的惊喜_css游戏

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部分:

z
z
y
y
x
x

复制代码

很简单的布局, 其中linexlineylinez是我画的坐标轴辅助线.

红线为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’;

break;

}

//赋值样式

for (let item in lastTransform) {

strTransfrom += item + ‘(’ + lastTransform[item] + ') ';

}

target.style.transform = strTransfrom;

复制代码

到这里, 我们的玩家盒子已经可以移动了.

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
img

最后

喜欢的话别忘了关注、点赞哦~

CodeChina开源项目:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

前端校招面试题精编解析大全

+ ') ';

}

target.style.transform = strTransfrom;

复制代码

到这里, 我们的玩家盒子已经可以移动了.

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-ljQmULDn-1712011978953)]
[外链图片转存中…(img-GvutZuc1-1712011978953)]
[外链图片转存中…(img-ghOXGKoA-1712011978954)]
[外链图片转存中…(img-YEqDEue0-1712011978954)]
[外链图片转存中…(img-QR98vvBW-1712011978954)]
[外链图片转存中…(img-poOtEgT1-1712011978955)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-pGKs6oDY-1712011978955)]

最后

喜欢的话别忘了关注、点赞哦~

CodeChina开源项目:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

前端校招面试题精编解析大全

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/834599
推荐阅读
相关标签
  

闽ICP备14008679号