我目前正在开发一个项目,在 Threejs 中对 Snake 进行编程(是的,我知道有更简单的方法)。一切似乎都工作正常,但当蛇达到一定大小时,CPU 使用率会跳至 50% 或更高,并且整个浏览器选项卡会冻结。
这是代码:JSFiddle (页面末尾有一个开始/重启按钮)
// Javascript
import * as THREE from "three";
import { gsap } from "https://cdn.skypack.dev/gsap";
const wh = window.innerHeight,
gamesize = 25,
mid = Math.floor(gamesize / 2),
scene = new THREE.Scene(),
light = new THREE.AmbientLight( 0x404040 ),
camera = new THREE.OrthographicCamera(-0.5, gamesize + 0.5, gamesize + 0.5, -0.5, 1, 100),
renderer = new THREE.WebGLRenderer({canvas: $("#threejs")[0]}),
game = {
snake: {
body: [],
length: 0,
d: "+x"
},
food: {},
running: true
},
snakeEvent = new Event("snake");
class CreateSnake {
constructor (x, y, d) {
this.x = x;
this.y = y;
this.z = 0;
this.d = d;
this.box = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshNormalMaterial()
);
};
getCoords () {
return [this.x, this.y, this.z];
};
getDirection () {
return this.d;
};
}
class CreateFood {
constructor () {
let c = validFoodLocations();
this.x = c[0];
this.y = c[1];
this.z = 0;
this.box = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshBasicMaterial({color: 0xff0000})
);
};
getCoords () {
return [this.x, this,y, this,z]
};
}
function init () {
$("#threejs").trigger("focus");
game.snake.body.push(new CreateSnake(mid, mid, "+x"));
game.snake.body[0].box.position.set(mid, mid, 0);
addFood();
scene.add(light, game.snake.body[0].box);
scene.background = new THREE.Color(0x1C3144);
camera.position.setZ(10);
camera.lookAt(0, 0, 0);
renderer.setPixelRatio(1/1);
renderer.setSize(wh, wh);
renderer.render(scene, camera);
addEventListener("resize", () => {renderer.setSize(window.innerHeight, window.innerHeight);});
addEventListener("keydown", keyDown);
game.gameloop = setInterval(gameloop, 100);
animate();
};
addEventListener("snake", init);
$("#restart").on("click", () => dispatchEvent(snakeEvent));
function gameloop () {
moveSnake();
if (compareCoords()) {
addBodyPart();
scene.remove(game.food.box);
addFood();
};
checkForDeath();
};
function moveSnake () {
let prev = "", x, y;
game.snake.body.forEach((part, i) => {
if (i === 0) {
x = part.x;
y = part.y;
prev = part.d;
switch (game.snake.d) {
case "+x":
part.x += 1;
break;
case "-x":
part.x -= 1;
break;
case "+y":
part.y += 1;
break;
case "-y":
part.y -= 1;
break;
default:
part.x += 1;
break;
};
gsap.fromTo(
part.box.position,
{x: x, y: y, duration: 0.1},
{x: x + (part.x - x), y: y + (part.y - y), duration: 0.1}
);
part.d = game.snake.d;
} else {
x = part.x;
y = part.y;
let d = prev
prev = part.d;
part.d = d;
switch (d) {
case "+x":
part.x += 1;
break;
case "-x":
part.x -= 1;
break;
case "+y":
part.y += 1;
break;
case "-y":
part.y -= 1;
break;
default:
part.x += 1;
break;
};
gsap.fromTo(
part.box.position,
{x: x, y: y, duration: 0.1},
{x: x + (part.x - x), y: y + (part.y - y), duration: 0.1}
);
};
});
};
function compareCoords () {
let s = game.snake.body[0],
f = game.food;
if (s.x === f.x && s.y === f.y && s.z === f.z) {
return true;
} else {
return false;
};
};
function addBodyPart () {
let b = game.snake.body,
l = b[b.length - 1],
c = l.getCoords(),
d = l.getDirection();
setTimeout(() => {
b.push(new CreateSnake(c[0], c[1], 1, d));
b[b.length - 1].box.position.set(c[0], c[1], 0);
scene.add(b[b.length - 1].box);
game.snake.length++;
}, 100);
};
function addFood () {
game.food = new CreateFood();
let f = game.food;
f.box.position.x = f.x;
f.box.position.y = f.y;
scene.add(f.box);
};
function validFoodLocations () {
let x, y;
while (!(x > 0 && x < gamesize && notInSnakeX(x))) {
x = Math.floor(Math.random() * gamesize);
};
while (!(y > 0 && y < gamesize && notInSnakeY(y))) {
y = Math.floor(Math.random() * gamesize);
};
return [x, y];
};
function notInSnakeX (x) {
let res = true;
game.snake.body.forEach(element => {
if (element.x === x) {
res = false;
};
});
return res;
};
function notInSnakeY (y) {
let res = true;
game.snake.body.forEach(element => {
if (element.y === y) {
res = false;
};
});
return res;
};
function keyDown (e) {
let t, d = game.snake.d;
switch(e.code) {
case "KeyA":
t = "-x";
break;
case "KeyW":
t = "+y";
break;
case "KeyD":
t = "+x";
break;
case "KeyS":
t = "-y";
break;
default:
d = d;
};
if (t !== undefined && !(t.charAt(1) === d.charAt(1) && t.charAt(0) !== d.charAt(0))) {
game.snake.d = t;
};
};
function animate () {
if (game.running) {
requestAnimationFrame(animate);
renderer.render(scene, camera);
};
};
function die () {
game.snake = {
body: [],
length: 0,
d: "+x"
};
game.food = {};
game.running = false;
clearInterval(game.gameloop);
delete game.gameloop;
};
function checkForDeath () {
let b = game.snake.body,
h = b[0];
if (h.x < 0 || h.x > gamesize || h.y < 0 || h.y > gamesize || checkForTouch(b, h)) {
die();
};
};
function checkForTouch (arr, v) {
let res = false;
arr.forEach((element, i) => {
if (i !== 0 && element.x === v.x && element.y === v.y) {
res = true;
};
});
return res;
};
额外信息:我已经在 Opera Gx 和 Edge 上进行了测试。当蛇有 31 个“身体部位”时,就会发生崩溃。
我尝试了很多事情,例如将所有内容拆分为不同的文件或删除一些渲染器功能,但没有帮助。 我还尝试将网格从 THREE.MeshNormalMaterial() 更改为 THREE.MeshBasicMateria(),然后我可以获得最多 41 个蛇部分。 (在 jsfiddle 中,我使用了 NormalMaterial,任何尝试重新创建错误的人都不必玩太久。) 我不知道是什么原因造成的,任何帮助都会很棒。
这是我所看到的,但不可能详尽无遗。
例如,您每次在身体的每个部位循环两次以检查 X 坐标,然后再次循环以检查 Y 坐标,您应该在身体部位循环一次并测试您需要的每个条件对本身的身体部位进行表演。
基本上,你有 X 个条件要测试。您不必循环使用 41 个身体部位并在同一循环中测试每个部位的状况。您将遇到的每种 X 情况都出现了 41 次。这是对资源的浪费,因为每次您可以处理单个对象上的单次加载可用的所有信息时,CPU 也必须从内存中重新加载信息。
我还会对蛇的身体使用 InstancedMesh,无需每次都创建一个全新的盒子几何体。您可以重复使用 GPU 上加载的第一个框的内容。
function notInSnakeX (x) {
let res = true;
game.snake.body.forEach(element => {
if (element.x === x) {
res = false;
};
});
return res;
};
function notInSnakeY (y) {
let res = true;
game.snake.body.forEach(element => {
if (element.y === y) {
res = false;
};
});
return res;
};