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();

运行结果

在这里插入图片描述

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小猴子喝牛奶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值