引言:
在前一篇博客中(实现护罩(防御罩、金钟罩、护盾)效果),我们介绍了如何使用 Three.js 生成一个动态护罩,并通过自定义 Shader 实现了护罩的光亮效果。在这篇博客中,我们将进一步扩展内容,介绍如何生成多个护罩,确保它们不会重叠,并且在屏幕上均匀分布。
实现技术简介:
本次实现我们依然依赖 Three.js。为了实现多个护罩,我们将会用到以下的技术:
- 位置生成与检测:生成多个护罩的位置,并确保它们之间保持一定的距离,不会重叠。
- Three.js 场景管理:在场景中添加多个护罩,并保持其动画效果。
第一步:调整基础场景与渲染器
在生成多个护罩前,我们需要确保场景、相机和渲染器已经设置完成,和之前的单个护罩没有太大区别。为了保持连贯性,我们可以重复之前的基础场景设置。
代码:
// 创建场景
const scene = new THREE.Scene();
// 设置透视相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 7, 10);
// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 添加环境光和方向光
const ambientLight = new THREE.AmbientLight(0xcccccc, 0.4);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(0, 5, 5);
scene.add(directionalLight);
解释:
与之前的设置一样,我们创建了 Three.js 的基础场景,包括相机、渲染器和光源。这些步骤将为生成多个护罩提供场景基础。
第二步:生成多个护罩
在生成多个护罩时,我们需要确保它们之间不会重叠。这要求在每个护罩生成时,检测新位置是否与已有护罩的位置冲突,如果发生重叠,则重新生成位置。我们将通过简单的随机位置生成,并利用 Vector3.distanceTo
来检测护罩之间的距离。
代码:
// 创建护罩,并确保它们不重叠
const createShields = (count: number) => {
const shieldPositions = []; // 存储护罩位置的数组
const shieldMeshes = []; // 存储护罩网格的数组
for (let i = 0; i < count; i++) {
const colors = [0x0000ff, 0xffff00]; // 护罩颜色数组
const randomHexColor = colors[Math.floor(Math.random() * colors.length)]; // 随机选择颜色
const shadermaterial = new ShieldMaterial(camera, renderer, 'hex.png', randomHexColor);
const sphereGeometry = new THREE.SphereGeometry(1.5, 64, 64);
const sphere = new THREE.Mesh(sphereGeometry, shadermaterial);
// 生成不重叠的随机位置
let position;
let overlapping;
do {
position = new THREE.Vector3(
(Math.random() - 0.5) * 30, // X轴
0.6, // Y轴固定在 0.6
(Math.random() - 0.5) * 30 // Z轴
);
// 检查护罩是否与已生成的护罩重叠
overlapping = shieldPositions.some(existingPosition => position.distanceTo(existingPosition) < 5);
} while (overlapping); // 如果重叠则重新生成位置
sphere.position.copy(position); // 将护罩放置在生成的位置
sphere.name = `Shield ${i + 1}`;
scene.add(sphere); // 添加到场景中
shieldPositions.push(position); // 保存位置
shieldMeshes.push(sphere); // 保存网格对象
}
};
解释:
- Vector3.distanceTo:用于检测两个
Vector3
(即护罩的位置)之间的距离,确保新生成的护罩位置与已有护罩之间的距离不小于 5 个单位。 - 递归生成:如果生成的护罩位置与已有护罩重叠,则重复生成,直到找到不重叠的位置。
- 多颜色支持:通过随机选择蓝色和黄色,确保每个护罩的颜色不同。
第三步:管理护罩动画
为保持护罩的动画效果,我们需要确保每个护罩都能够按照时间动态变化。我们会通过动画循环来持续更新每个护罩的 time
值,生成类似于“闪烁”效果的动态护罩。
代码:
// 动画循环
const clock = new THREE.Clock(); // 用于跟踪时间
const animate = () => {
requestAnimationFrame(animate); // 递归调用 animate 进行动画渲染
const elapsedTime = clock.getElapsedTime(); // 获取时间流逝
// 更新每个护罩的动画时间
shieldMeshes.forEach(shield => {
shield.material.uniforms.time.value = elapsedTime;
});
// 渲染场景
renderer.render(scene, camera);
};
animate(); // 开始动画
解释:
- Clock:用于获取流逝的时间,使护罩材质中的
time
随时间变化,从而产生动态效果。 - requestAnimationFrame:确保以最佳帧率不断渲染场景和护罩的动画。
第四步:将护罩与标签绑定
为了便于理解每个护罩的作用,我们可以为每个护罩添加标签。通过 CSS2DRenderer
,我们可以将 HTML 标签与护罩绑定,使标签跟随护罩移动。
代码:
const createShieldLabel = (shield, index) => {
const div = document.createElement('div');
div.className = 'label';
div.textContent = `Shield ${index + 1}`;
div.style.fontSize = '14px';
div.style.color = 'white';
div.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
div.style.padding = '5px';
div.style.borderRadius = '5px';
const label = new CSS2DObject(div);
label.position.set(0, 2, 0); // 标签位于护罩上方
shield.add(label); // 将标签绑定到护罩
};
// 在创建每个护罩后调用此函数
shieldMeshes.forEach((shield, index) => {
createShieldLabel(shield, index);
});
解释:
- CSS2DObject:允许我们使用 HTML 标签来为每个护罩生成可视化的说明,便于展示护罩的编号或其他信息。
- 绑定标签:通过
add
方法将标签绑定到护罩,使标签跟随护罩一起移动。
总结:
通过以上步骤,我们成功生成了多个不重叠的护罩,并为每个护罩添加了动态效果。护罩之间保持一定的距离,并且在生成时防止重叠。你可以通过修改护罩的颜色、位置和动画参数来调整效果。
在接下来的博客中,我们将探讨如何进一步增强这些护罩的视觉效果,比如添加更多炫酷的飞线特效或是让护罩在受到攻击时产生更具冲击力的视觉反馈。