Threejs系列--19游戏开发--沙漠赛车游戏【开始按钮鼠标交互之光标移入提示】
序言
本章将实现鼠标的交互效果。
目录结构
资源目录里面的结构不变,点击传送门快速查看。
|__src
|__assets
|__js
| |__base 基础类文件夹
| |__Camera.js 相机类
| |__Resources.js 资源类
| |__geometries 定制的物体类文件夹
| |__AreaFenceBufferGeometry.js 【新增--开始按钮的栏栅模型】
| |__AreaFloorBorderBufferGeometry.js 进度条几何体
| |__materials 材质类文件夹
| |__Floor.js 地面材质
| |__AreaFence.js 【新增--开始按钮的栏栅材质】
| |__AreaFloorBorder.js 进度条着色器
| |__passes 合成器通道文件夹
| |__Blur.js 模糊着色器
| |__Glows.js 发光着色器
| |__utils 工具类文件夹
| |__Sizes.js 画布大小控制类
| |__EventEmitter.js 基础事件处理器
| |__Time.js 动画刷新 【新增--记录事件,计算栏栅的移动】
| |__Loader.js 加载器
| |__world 精灵类文件夹
| |__Area.js 区域基础类 【新增--栏栅】
| |__Areas.js 区域管理类 【新增--鼠标交互】
| |__index.js 精灵类
| |__Floor.js 地面类
| |__Application.js 初始化游戏的文件
|__index.js 入口
|__index.css 小项目,样式一丢丢
代码一览
Time.js代码
...
export default class Time extends EventEmitter {
constructor(){
super();
this.start = Date.now();
this.elapsed = 0;
...
}
/**
* 刷新
*/
tick(){
const current = Date.now();
this.elapsed = current - this.start;
...
}
...
}
AreaFenceBufferGeometry.js代码
import * as THREE from "three";
class AreaFenceBufferGeometry {
constructor(_width, _height, _depth) {
this.parameters = {
width: _width,
height: _height,
depth: _depth,
};
this.type = "AreaFloorBufferGeometry";
const length = 8;
const vertices = new Float32Array(length * 3);
const uvs = new Uint32Array(length * 2);
const indices = new Uint32Array(length * 6);
// 顶点
vertices[0 * 3 + 0] = _width * 0.5;
vertices[0 * 3 + 1] = _height * 0.5;
vertices[0 * 3 + 2] = 0;
vertices[1 * 3 + 0] = _width * 0.5;
vertices[1 * 3 + 1] = -_height * 0.5;
vertices[1 * 3 + 2] = 0;
vertices[2 * 3 + 0] = -_width * 0.5;
vertices[2 * 3 + 1] = -_height * 0.5;
vertices[2 * 3 + 2] = 0;
vertices[3 * 3 + 0] = -_width * 0.5;
vertices[3 * 3 + 1] = _height * 0.5;
vertices[3 * 3 + 2] = 0;
vertices[4 * 3 + 0] = _width * 0.5;
vertices[4 * 3 + 1] = _height * 0.5;
vertices[4 * 3 + 2] = _depth;
vertices[5 * 3 + 0] = _width * 0.5;
vertices[5 * 3 + 1] = -_height * 0.5;
vertices[5 * 3 + 2] = _depth;
vertices[6 * 3 + 0] = -_width * 0.5;
vertices[6 * 3 + 1] = -_height * 0.5;
vertices[6 * 3 + 2] = _depth;
vertices[7 * 3 + 0] = -_width * 0.5;
vertices[7 * 3 + 1] = _height * 0.5;
vertices[7 * 3 + 2] = _depth;
// uv
uvs[0 * 2 + 0] = 0;
uvs[0 * 2 + 1] = 0;
uvs[1 * 2 + 0] = 1 / 3;
uvs[1 * 2 + 1] = 0;
uvs[2 * 2 + 0] = (1 / 3) * 2;
uvs[2 * 2 + 1] = 0;
uvs[3 * 2 + 0] = 1;
uvs[3 * 2 + 1] = 0;
uvs[4 * 2 + 0] = 0;
uvs[4 * 2 + 1] = 1;
uvs[5 * 2 + 0] = 1 / 3;
uvs[5 * 2 + 1] = 1;
uvs[6 * 2 + 0] = (1 / 3) * 2;
uvs[6 * 2 + 1] = 1;
uvs[7 * 2 + 0] = 1;
uvs[7 * 2 + 1] = 1;
// 索引
indices[0 * 3 + 0] = 0;
indices[0 * 3 + 1] = 4;
indices[0 * 3 + 2] = 1;
indices[1 * 3 + 0] = 5;
indices[1 * 3 + 1] = 1;
indices[1 * 3 + 2] = 4;
indices[2 * 3 + 0] = 1;
indices[2 * 3 + 1] = 5;
indices[2 * 3 + 2] = 2;
indices[3 * 3 + 0] = 6;
indices[3 * 3 + 1] = 2;
indices[3 * 3 + 2] = 5;
indices[4 * 3 + 0] = 2;
indices[4 * 3 + 1] = 6;
indices[4 * 3 + 2] = 3;
indices[5 * 3 + 0] = 7;
indices[5 * 3 + 1] = 3;
indices[5 * 3 + 2] = 6;
indices[6 * 3 + 0] = 3;
indices[6 * 3 + 1] = 7;
indices[6 * 3 + 2] = 0;
indices[7 * 3 + 0] = 4;
indices[7 * 3 + 1] = 0;
indices[7 * 3 + 2] = 7;
//创建一个Buffer类型几何体对象
const geometry = new THREE.BufferGeometry();
// 创建属性缓冲区对象
geometry.setIndex(new THREE.BufferAttribute(indices, 1, false));
// Set attributes
geometry.setAttribute(
"position",
new THREE.Float32BufferAttribute(vertices, 3)
);
// 设置几何体attributes属性的位置属性
geometry.setAttribute("uv", new THREE.Float32BufferAttribute(uvs, 2));
return geometry;
}
}
export default AreaFenceBufferGeometry;
AreaFence.js代码
import * as THREE from "three";
import shaderFragment from "../../assets/shaders/areaFence/fragment.glsl";
import shaderVertex from "../../assets/shaders/areaFence/vertex.glsl";
export default function () {
const uniforms = {
uTime: { value: null },
uBorderAlpha: { value: null },
uStrikeAlpha: { value: null },
};
const material = new THREE.ShaderMaterial({
wireframe: false,
transparent: true,
side: THREE.DoubleSide,
depthTest: true,
depthWrite: false,
uniforms,
vertexShader: shaderVertex,
fragmentShader: shaderFragment,
});
return material;
}
Area.js代码
import * as THREE from 'three';
import gsap from 'gsap';
import EventEmitter from "../utils/EventEmitter";
import AreaFloorBorderBufferGeometry from '../geometries/AreaFloorBorderBufferGeometry';
import AreaFloorBorderMaterial from '../materials/AreaFloorBorder';
import AreaFenceBufferGeometry from '../geometries/AreaFenceBufferGeometry';
import AreaFenceMaterial from '../materials/AreaFence';
export default class Area extends EventEmitter{
constructor(_options){
super();
this.time = _options.time;
this.position = _options.position;
this.halfExtents = _options.halfExtents;
this.container = new THREE.Object3D();
this.container.position.x = this.position.x;
this.container.position.y = this.position.y;
this.container.matrixAutoUpdate = false;
this.container.updateMatrix();
this.setFloorBorder();
this.setfence();
this.setInteractions();
}
//隐藏栏栅
out(){
gsap.killTweensOf(this.fence.mesh.position);
gsap.to(this.fence.mesh.position, { duration: 0.35, ease: "back.out(4)", z: -this.fence.offset })
}
//展示栏栅
in(){
gsap.killTweensOf(this.fence.mesh.position);
gsap.to(this.fence.mesh.position, { duration: 0.35, ease: "back.out(3)", z: this.fence.offset })
}
//光标移入的目标区域,可选择添加 可给材质设置颜色 例如红色,不透明 运行效果图1
setInteractions(){
this.mouseMesh = new THREE.Mesh(
new THREE.PlaneBufferGeometry(this.halfExtents.x * 2, this.halfExtents.y * 2),
new THREE.MeshBasicMaterial({ transparent: true, opacity: 0 })
);
this.mouseMesh.position.z = -0.01;
this.mouseMesh.matrixAutoUpdate = false;
this.mouseMesh.updateMatrix();
this.container.add(this.mouseMesh)
}
...
//栏栅方法
setfence(){
this.fence = {};
this.fence.depth = 0.5;
this.fence.offset = 0.5;
this.fence.gromerty = new AreaFenceBufferGeometry(
this.halfExtents.x * 2,
this.halfExtents.y * 2,
this.fence.depth
);
this.fence.material = new AreaFenceMaterial();
this.fence.material.uniforms.uBorderAlpha.value = 0.5;
this.fence.material.uniforms.uStrikeAlpha.value = 0.25;
this.fence.mesh = new THREE.Mesh(this.fence.gromerty, this.fence.material);
this.fence.mesh.position.z = -this.fence.depth;
this.container.add(this.fence.mesh);
this.time.on('tick', () => {
this.fence.material.uniforms.uTime.value = this.time.elapsed;
})
}
}
Areas.js代码
import * as THREE from 'three';
import Area from "./Area";
export default class Areas {
constructor(_options) {
// this.renderer = _options.renderer;
this.time = _options.time;
this.camera = _options.camera;
this.resources = _options.resources;
this.items = [];
this.container = new THREE.Object3D();
this.container.matrixAutoUpdate = false;
this.setMouse();
}
//鼠标交互方法
setMouse(){
this.mouse = {};
this.mouse.currentArea = null;
//光线投射类用于进行鼠标拾取
this.mouse.raycaster = new THREE.Raycaster();
//存放鼠标坐标
this.mouse.coordinates = new THREE.Vector2();
window.addEventListener('mousemove', (_event) => {
//位置归一化处理 鼠标位置转为设备坐标
this.mouse.coordinates.x = (_event.clientX / window.innerWidth) * 2 - 1;
this.mouse.coordinates.y = -(_event.clientY / window.innerHeight) * 2 + 1;
});
this.time.on('tick', () => {
//通过相机与鼠标位置更新射线
this.mouse.raycaster.setFromCamera(
this.mouse.coordinates,
this.camera.instance
);
//获取命中目标
const intersects = this.mouse.raycaster.intersectObjects(
this.items.map(_area => _area.mouseMesh)
);
//判断是否为移入目标,展示或隐藏栅栏
if(intersects.length){
const area = this.items.find(_area => _area.mouseMesh === intersects[0].object);
if(area != this.mouse.currentArea){
if(this.mouse.currentArea != null){
this.mouse.currentArea.out();
}
this.mouse.currentArea = area;
this.mouse.currentArea.in();
}
}else if(this.mouse.currentArea != null){
this.mouse.currentArea.out();
this.mouse.currentArea = null;
}
});
}
add(_options) {
const area = new Area({
time: this.time,
resources: this.resources,
..._options,
});
this.container.add(area.container);
this.items.push(area);
return area;
}
}
index.js代码
//传递time与camera对象,记得在构造函数内接受
setAreas(){
this.areas = new Areas({
time: this.time, 新增
camera: this.camera, 新增
resources: this.resources
})
this.container.add(this.areas.container)
}
Application.js代码
//传递time与camera对象,记得在构造函数内接受
setWorld(){
this.world = new World({
time: this.time, //新增
camera: this.camera, //新增
resources: this.resources
});
this.scene.add(this.world.container);
}
代码解读
Application.js与index.js将需要的time与camera对象传递给Areas.js
Time.js新增了记录时间,在片元着色器中计算栅栏中的动画
Areas.js 构造鼠标移入需要处理的业务
Area.js 构造具体的移入移出效果,并且实现产生效果需要展示的模型
AreaFence.js与AreaFenceBufferGeometry.js实现了改模型
通过控制z轴来实现隐藏与展示
具体的交互方式使用threejs提供的光线投射Raycaster,用于进行鼠标拾取。
核心代码:
//获取光线投射对象
var raycaster = new THREE.Raycaster();
//准备存储鼠标坐标 需要转设备坐标
var mouse = new THREE.Vector2();
// 通过摄像机和鼠标位置更新射线 参数传入光标位置与相机对象
raycaster.setFromCamera();
// 计算物体和射线的焦点 参数传入3d模型对象
raycaster.intersectObjects();