Threejs系列--10游戏开发--沙漠赛车游戏【基础事件处理器】

序言

通过上一章,你可以将图形渲染出来,但是没有任何动画效果。本章将进行动画事件的基本处理,让图像可以在页面内运动。

目录结构

资源目录里面的结构不变,点击传送门快速查看。

|__src
	|__assets
	|__js
	|	|__base		基础类文件夹
	|		|__Camera.js 相机类
	|	|__geometries	定制的物体类文件夹
	|	|__materials	材质类文件夹
	|	|__passes	合成器通道文件夹
	|	|__utils	工具类文件夹
	|		|__Sizes.js  画布大小控制类
	|		|__EventEmitter.js 基础事件处理器 【新增--处理事件队列】
	|		|__Time.js  动画刷新 【新增--处理动画的刷新与停止】
	|	|__world	精灵类文件夹
	|	|__Application.js	初始化游戏的文件 【test方法中新增刷新事件】
	|__index.js		入口
	|__index.css  	小项目,样式一丢丢

代码一览

新增了两个文件,以及Application中test对其进行了测试。

Time.js代码

import EventEmitter from "./EventEmitter";

export default class Time extends EventEmitter {
    constructor(){
        super();

        this.tick = this.tick.bind(this);
        this.tick()
    }

    /**
     * 屏幕刷新
     */
    tick(){
        window.requestAnimationFrame(this.tick);
        
        this.trigger("tick");
    }

    /**
     * 停止刷新
     */
    stop(){}
}

EventEmitter.js代码

/**
 * 事件处理器
 */
export default class {
    constructor(){
        //事件队列
        this.callbacks = {};
        this.callbacks.base = {};
    }

    /**
     * 绑定事件
     * @param {*} _names 
     * @param {*} callback 
     * @returns 
     */
    on(_names, callback) {

        if(typeof _names === "undefined" && _names == "") {
            console.warn("事件名异常");
            return false;
        }

        if(typeof callback !== "function"){
            console.warn("事件回调异常");
            return false;
        }

        //处理事件名列表
        const names = this.resolveNames(_names);

        //将事件添加到回调队列
        names.forEach(_name => {
            //处理单个事件名
            const name = this.resolveName(_name);

            if(!(this.callbacks[name.namespace] instanceof Object)){
                this.callbacks[name.namespace] = {};
            }

            if(!(this.callbacks[name.namespace][name.value] instanceof Array)){
                this.callbacks[name.namespace][name.value] = [];
            }

            this.callbacks[name.namespace][name.value].push(callback);
        });

        return this;
    }

    /**
     * 卸载事件
     */
    off(_names) {
        if(typeof _names === "undefined" || _names === ""){
            console.warn("事件名异常");
            return false;
        }

        const names = this.resolveNames(_names);

        names.forEach(_name => {
            const name = this.resolveName(_names);

            if (name.namespace !== "base" && name.value === "") {
                delete this.callbacks[name.namespace];
                return;
            }

            if (name.namespace === "base") {
                for (const namespace in this.callbacks) {
                    if (
                        this.callbacks[namespace] instanceof Object &&
                        this.callbacks[namespace][name.value] instanceof Array
                    ) {
                    delete this.callbacks[namespace][name.value];
        
                    if (Object.keys(this.callbacks[namespace]).length === 0)
                        delete this.callbacks[namespace];
                    }
                }
                return;
            }

            
            if (
                this.callbacks[name.namespace] instanceof Object &&
                this.callbacks[name.namespace][name.value] instanceof Array
            ) {
                delete this.callbacks[name.namespace][name.value];

                if (Object.keys(this.callbacks[name.namespace]).length === 0) {
                     delete this.callbacks[name.namespace];
                }
            }
        })

        return this;
    }

    /**
     * 触发事件
     * @param {string} _name 
     * @param {array} _args 
     */
    trigger(_name, _args) {
        if (typeof _name === "undefined" || _name === "") {
            console.warn("事件名异常");
            return;
        }

        const args = !(_args instanceof Array) ? [] : _args;

        let name = this.resolveNames(_name);

        name = this.resolveName(name[0]);

        if (name.namespace === "base") {
            for(const namespace in this.callbacks){
                if(
                    this.callbacks[namespace] instanceof Object &&
                this.callbacks[namespace][name.value] instanceof Array
                ){
                    this.callbacks[namespace][name.value].forEach(callback => {
                        callback.apply(this, args);
                    })
                }
            }
            return;
        }
        console.log(this.callbacks[name.namespace] )
        if (this.callbacks[name.namespace] instanceof Object) {
            if (name.value === "") {
                console.warn("事件名异常");
                return;
            }

            this.callbacks[name.namespace][name.value].forEach(callback => {
                callback.apply(this, args);
            })
        }
    }

    /**
     * 处理事件名列表
     * @param {strinng} _names 
     * @returns 
     */
    resolveNames(_names) {
        let names = _names;

        names = names.replace(/[^a-zA-Z0-9 ,/.]/g, "");
        names = names.replace(/[,/]+/g, " ");
        names = names.split(" ");

        return names;
    }

    /**
     * 处理事件名
     * @param {string} name 
     * @returns 
     */
    resolveName(name) {
        const newName = {};
        const parts = name.split(".");

        newName.original = name;
        newName.value = parts[0];
        newName.namespace = "base";

        if(parts.length > 1 && parts[1] !== ""){ 
            newName.namespace = parts[1];
        }

        return newName;
    }
}

Sizes.js代码

/**
 * 游戏窗口
 */
export default class Sizes {
  constructor() {
    this.viewport = {};
    this.$sizeViewport = document.createElement("div");
    this.$sizeViewport.style.width = "100vw";
    this.$sizeViewport.style.height = "100vh";
    this.$sizeViewport.style.position = "absolute";
    this.$sizeViewport.style.top = 0;
    this.$sizeViewport.style.left = 0;
    this.$sizeViewport.style.pointerEvents = "none";
  
    //监听resize

    //触发resize
    this.resize();
  }

  resize() {
    document.body.appendChild(this.$sizeViewport);
    this.viewport.width = this.$sizeViewport.offsetWidth;
    this.viewport.height = this.$sizeViewport.offsetHeight;
    document.body.removeChild(this.$sizeViewport);

    //对resize的定制
  }
}

Application.js代码

import React from 'react';
import ReactDOM from 'react-dom';
import * as THREE from "three";
import Camera from './base/Camera';
import Sizes from './utils/Sizes';
import Time from './utils/Time';

export default class Application extends React.Component {
  componentDidMount(){
    //当前画布对象
    this.$canvas = ReactDOM.findDOMNode(this);
	
	//对于画面的交互事件处理
    this.time = new Time();

    // //针对场景大小控制的类
    this.sizes = new Sizes();

    //导入资源
    this.resources = new Resources()
    
    //基础配置
    this.setConfig();

    //设置调试工具
    this.setDebug();

    //设置场景构造器
    this.setRenderer();

    //设置相机
    this.setCamera();

    //使用通道设置高级处理效果
    this.setPasses();

    //设置精灵
    this.setWorld();

	
    //测试场景
    this.test()
  }

  setConfig(){}

  setDebug(){}

  setCamera(){
    //获取相机
    this.camera = new Camera({
      sizes: this.sizes,
      renderer: this.renderer,
    });

    //相机实例添加到场景中
    this.scene.add(this.camera.container);
  }

  setRenderer(){
    //获取场景
    this.scene = new THREE.Scene();

    //获取构造器
    this.renderer = new THREE.WebGLRenderer({
      canvas: this.$canvas,  //一个供渲染器绘制其输出的canvas。如果没有传这个参数,会创建一个新canvas
      alpha: true,  // canvas是否包含透明度
    });

    //构造器设置
    this.renderer.setClearColor(0x000000, 1); //设置颜色及其透明度
    this.renderer.setPixelRatio(2); //设置设备像素比。通常用于避免HiDPI设备上绘图模糊
    this.renderer.setSize(
      this.sizes.viewport.width,
      this.sizes.viewport.height
    ); //将输出canvas的大小调整为(width, height)并考虑设备像素比,。
    this.renderer.physicallyCorrectLights = true; //是否使用物理上正确的光照模式
    this.renderer.gammaFactor = 2.2;  //默认2
    this.renderer.gammaOutPut = true; //如果设置, 所有纹理和颜色需要乘以gamma输出
    this.renderer.autoClear = false; //定义渲染器是否在渲染每一帧之前自动清除其输出
  }

  setPasses(){}

  setWorld(){}


  test(){
    const geometry = new THREE.BoxGeometry();
	const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
	const cube = new THREE.Mesh( geometry, material );
	this.scene.add( cube );

	this.camera.instance.position.z = 5;
    this.time.on("tick", () => {
      this.camera.instance.position.z  += 0.02; 
      this.renderer.render(this.scene, this.camera.instance)  
    });
    
  }

  render(){
    return (
      <canvas id="webgl"></canvas>
    );
  }
}

代码解读

EventEmitter.js是一个事件处理的基础类,可以称为事件管理器。
on() 进行事件绑定
off() 进行事件卸载
trigger() 进行事件触发
所有的事件都添加在属性 callbacks 这个对象上。通过对象内的属性区分是否基础事件,或者其它事件。每个对象内维护一个事件队列。

Time.js是一个处理画面刷新的类。
tick() 刷新画面
stop() 停止刷新
Time类继承EventEmitter类,以后页面中需要对事件进行处理,可以使用time对象。

例如:在入口的test方法中绑定了页面刷新的事件,接着在页面刷新中不断触发刷新方法,就会看到动画效果了。

运行结果

绿色的矩形块将会有大变小

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小猴子喝牛奶

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

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

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

打赏作者

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

抵扣说明:

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

余额充值