Threejs系列--9游戏开发--沙漠赛车游戏【基础事件处理器】
序言
通过上一章,你可以将图形渲染出来,但是没有任何动画效果。本章将进行动画事件的基本处理,让图像可以在页面内运动。
目录结构
资源目录里面的结构不变,点击传送门快速查看。
|__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方法中绑定了页面刷新的事件,接着在页面刷新中不断触发刷新方法,就会看到动画效果了。
运行结果
绿色的矩形块将会有大变小