携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第32天,点击查看活动详情
上一篇文章中讲到了这个推箱子小游戏的初始化部分,今天我们来看看场景的生成部分。
场景生成
当年写的代码居然还有面向对象的方式在里面,而且还用的是Funcion来写的一个class(应该是当时还没太听说过es6),主要就是三个部分,基本方块类、人物方块类和终点方块类。
基本方块类
基本方块类的代码如下:
/*
基本方块类
*/
function Cube(state, row, column, materialurl, x, y, z) {
this.state = state; //状态 1墙体 2箱子
this.row = row; //所在行
this.column = column; //所在列
if (state == 1 || state == 2) {
var cubegeometry = new THREE.BoxGeometry(10, 10, 10); //方块大小固定
if (materialurl == "crate") {
var cubematerial = new THREE.MeshBasicMaterial({
map: textureloader.loadTexture('img/crate.jpg')
});
} else {
var cubematerial = new THREE.MeshBasicMaterial({
map: textureloader.loadTexture('img/bricks.jpg')
});
}
var cube = new THREE.Mesh(cubegeometry, cubematerial);
cube.position.set(x, y, z);
this.cube = cube; //将网孔对象传出以便移动
scene.add(cube);
}
}
基本方块类主要包括两种方块,墙体和箱子,首先构建一个正方体BoxGeometry对象,,之后根据不同的类型加载不同的贴图生成MeshBasicMaterial对象在正方体的六个面都贴上图片,最后让二者结合生成Mesh对象后,将其添加到场景中,并将这个对象暴露在Cube对象的cube属性中,以便后续的移动操作。
人物方块类
人物方块类的代码如下:
/*
人物方块类
*/
function PersonCube(row, column) {
this.state = 3; //3代表人物
this.row = row;
this.column = column;
var loader = new THREE.STLLoader();
var ghostMaterial = new THREE.MeshPhongMaterial({
color:0xffffff
});
loader.load("img/Fantasma.stl", function(obj) {
var cube = new THREE.Mesh(obj, ghostMaterial);
cube.rotateX(Math.PI / 2 * 3);
cube.position.set((column - 1) * 10 + 5, 0, (row - 1) * 10 + 5);
cube.scale.set(3, 3, 3);
scene.add(cube);
ghost.cube = cube;
document.getElementById("white").style.display="none";
})
this.isavailable = function(direction) {
row = this.row;
column = this.column;
var availarr = [0, 4]; //可以直接走的标记
switch (direction) {
case "w":
if (cubes[row - 1] && availarr.includes(cubes[row - 1][column].state)) {
return true;
}
if (cubes[row - 2] && availarr.includes(cubes[row - 2][column].state)) {
if (cubes[row - 1][column].state == 2) {
return true;
}
}
return false;
case "s":
if (availarr.includes(cubes[row + 1] && cubes[row + 1][column].state)) {
return true;
}
if (availarr.includes(cubes[row + 2] && cubes[row + 2][column].state)) {
if (cubes[row + 1][column].state == 2) {
return true;
}
}
return false;
case "a":
if (availarr.includes(cubes[row][column - 1] && cubes[row][column - 1].state)) {
return true;
}
if (availarr.includes(cubes[row][column - 2] && cubes[row][column - 2].state)) {
if (cubes[row][column - 1].state == 2) {
return true;
}
}
return false;
case "d":
if (availarr.includes(cubes[row][column + 1] && cubes[row][column + 1].state)) {
return true;
}
if (availarr.includes(cubes[row][column + 2] && cubes[row][column + 2].state)) {
if (cubes[row][column + 1].state == 2) {
return true;
}
}
return false;
default:
console.log("direction error");
}
}
}
与基础方块类不同的是,人物类不是采用了正方体进行贴图,而是加载了一个stl模型,再构建一个MeshPhongMaterial对象为其上色,上色后依然将二者组合为Mesh对象,添加到场景中。
此外,人物方块多了一个属性isAvailable,用来判断在各个方向是否可以移动。
- 前方无箱子和墙方块,可以移动
- 前方有箱子方块,则进一步判断下一个位置是否有方块,若没有,则可以移动
- 其他情况下不可移动
终点方块类
终点方块类的代码如下:
/*
终点方块类
*/
function DesCube(i, j) {
desCubes = new Array();//清空完成数组
var textureloader = new THREE.TextureLoader();
var loader = new THREE.STLLoader();
var ghostMaterial = new THREE.MeshBasicMaterial({
color: 0x2033a4,
});
loader.load("img/Base_luz.stl", function(obj) {
var cube = new THREE.Mesh(obj, ghostMaterial);
cube.rotateX(Math.PI / 2 * 3);
cube.position.set((j - 1) * 10 + 5, 0, (i - 1) * 10 + 5);
cube.scale.set(3, 3, 3);
scene.add(cube);
// boxhelper = new THREE.BoxHelper(cube);
// scene.add(boxhelper);
desCubes.push({
state: 0, //0代表未完成 1代表完成
position: {
row: i,
column: j
}
});
});
}
DesCube.prototype.checkcube = function(row, column) {
for (var i = 0; i < desCubes.length; i++) { //判断单个箱子是否完成
if (row == desCubes[i].position.row && column == desCubes[i].position.column) {
desCubes[i].state = 1;
return true; //表示已到位
}
}
return false;
}
DesCube.prototype.checkdes = function() {
for (var i = 0; i < desCubes.length; i++) {
if (desCubes[i].position.row == ghost.row && desCubes[i].position.column == ghost.column) { //幽灵若在目标点则证明箱子被推出去了,此点未完成
desCubes[i].state = 0;
}
}
for (var i = 0; i < desCubes.length; i++) {
if (desCubes[i].state == 0) {
return false;
}
}
return true;
};
与人物方块类类似,终点方块也进行了模型的加载和上色,而终点方块多了两个方法,一个是checkcube用以检查当前终点位置是否与箱子位置重合,若重合则表示箱子已经推到位,进行相应处理(将箱子变小);另一个是关卡检查方法checkdes当有箱子推到位时,进行一次关卡的检查,检查是否所有的箱子都已经就为,如果完成则显示通关提示。
各个方块的初始化
定义好各个方块的类和方法之后,就可以开始着手初始化关卡了:
/*
元素绘制
*/
//网格线的绘制
function initmeshline() {
for (var i = 0; i <= 100; i += 10) {
//线长度固定
var lineContainer = new THREE.Geometry();
lineContainer.vertices.push(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, 100));
//与z轴平行的线
var line = new THREE.Line(lineContainer, new THREE.LineBasicMaterial());
scene.add(line);
line.position.set(i, 0, 0);
// 与x轴平行的线
var rotateline = new THREE.Line(lineContainer, new THREE.LineBasicMaterial());
rotateline.rotateY(Math.PI / 2);
rotateline.position.set(0, 0, i);
scene.add(rotateline);
}
}
temparray = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 4, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 2, 0, 2, 4, 1, 0, 0, 1, 4, 0, 2, 3, 1, 1, 1, 0, 0, 1, 1, 1, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 1, 4, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
//方块生成,使用二维数组,封装成函数以便地图加载时候调用
function initcube(temparray) {
scene.visible = true;
document.body.style.backgroundImage = "url(img/hacker.jpg)"; //恢复网页背景
cubes = new Array(); //清空之前数据
var tempindex = 0;
for (var i = 1; i <= 10; i++) {
cubes[i] = new Array(); //避免一维数组未初始化时调用二维索引出错
for (var j = 1; j <= 10; j++) {
// var index = parseInt(Math.random() * 3);
var index = temparray[tempindex];
switch (index) {
case 4:
descube = new DesCube(i, j);
cubes[i][j] = {
state: 4
};
tempindex++;
break;
case 3:
ghost = new PersonCube(i, j);
cubes[i][j] = {
state: 3
}; //更新数组
tempindex++;
break;
case 2:
cubes[i][j] = new Cube(index, i, j, 'crate', (j - 1) * 10 + 5, 5, (i - 1) * 10 + 5);
// scene.add(cubes[i][j].cube);
tempindex++;
break;
case 1:
cubes[i][j] = new Cube(index, i, j, 'bricks', (j - 1) * 10 + 5, 5, (i - 1) * 10 + 5);
// scene.add(cubes[i][j].cube);
tempindex++;
break;
case 0:
cubes[i][j] = new Cube(0, i, j);
tempindex++;
default:
continue;
}
}
}
}
首先是进行网格线的绘制,没啥说的,基本就是循环遍历生成Line对象,并将其添加到场景之中。
之后的方块生成方法传入了一个100个元素长度的一维数组,遍历将其生成为二位数组,之后根据类型将对应的方块实例化并保存到cubes二维数组中,以便之后进行移动、判断终点之类的处理。(为什么没有使用scene.add方法将方块对象添加到场景中呢?因为在方块类实例化中就已经完成了这一步骤了)
而之所以这里还要传一个参数,一方面是为了关卡切换,另一方面还加入了自定义关卡的功能,通过这个100个长度的数组可以自定义关卡:
//根据载入数据生成地图
function initmap(arr) {
if (arr.length == 100) {
for (var i = 0; i < arr.length; i++) { //字符串转化为整数以便识别
arr[i] = parseInt(arr[i]);
}
for (var i = 0; i < scene.children.length; i++) {
if (scene.children[i] && scene.children[i].type == "Mesh") {
scene.remove(scene.children[i]); //移除原有元素
i--; //防止元素跳过
}
}
step = [];
stepflag = []; //重置步骤
temparray = arr; //将数组存入全局变量以便刷新使用
initcube(temparray); //重新生成元素
document.getElementById("num").innerHTML = step.length; //更新步骤显示
} else {
swal('地图文件格式错误', '请读入正确的地图文件', 'error');
}
}