飞驰人生模拟器-基于threejs的赛车模拟小游戏

今天刷到了一篇文章,用threejs十行代码实现游戏场景中的动态道路,效果非常炫酷,还可以控制加减速,有点玩赛车游戏的感觉了,但是空旷的赛道总觉得缺点什么,正好最近在学习threejs,于是就想在此基础上实现一款赛车模拟小游戏,代码比较简单,纯属娱乐。

1.导入场景

这一步是基于参考文章中引入shader代码实现动态赛道场景

ini 复制代码
import * as THREE from 'three';

let _b = 9/16;
let _w = 900;
let _h = 900*_b;
let car1;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(40, _w / _h, 1, 1000);

camera.position.z = 112;

const vertexShader = `
        varying vec2 vUv;
        void main(){
        vUv = uv;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
    `;

let fragmentShader = `
    uniform float iTime;
    varying vec2 vUv;
    
    void main() {
        vec2 p = vUv;
        vec3 q=vec3(1.0,1.0,1.0),d=vec3(p-.5*q.xy,q.y)/q.y,c=vec3(0,.5,.7);
        q=d/(.1-d.y);
        float a=iTime, k=sin(.2*a), w = q.x *= q.x-=.05*k*k*k*q.z*q.z;
        vec3 col = vec3(0);
        col.xyz=d.y>.04?c:
            sin(4.*q.z+40.*a)>0.?
            w>2.?c.xyx:w>1.2?d.zzz:c.yyy:
            w>2.?c.xzx:w>1.2?c.yxx*2.:(w>.004?c:d).zzz;
        gl_FragColor = vec4(col,1.0);
    }
    `;

let width = 160;
const geometry = new THREE.PlaneGeometry(width,width*_b)

const material = new THREE.ShaderMaterial({
    uniforms: {
        iTime: { value: 0 },
    },
    fragmentShader: fragmentShader,
    vertexShader: vertexShader,
});

const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

const renderer = new THREE.WebGLRenderer();
renderer.setSize(_w,_h);

// new OrbitControls(camera, renderer.domElement);

renderer.setClearColor(0xFFFFFF, 1.0);
document.body.appendChild(renderer.domElement);

2.导入汽车模型和纹理加载器

  • 此步骤引入了一个glb汽车模型和hdr纹理。
  • glb是在sketchfab.com/feed 里面下载的。
  • hrd文件能够更好地表现真实的光照和阴影等细节,为了让车模型看起来更真实。
ini 复制代码
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';

new RGBELoader()
		.setPath( './assets/model/textures/equirectangular/' )
		.load( 'blouberg_sunrise_2_1k.hdr', function ( texture ) {
			texture.mapping = THREE.EquirectangularReflectionMapping;
			scene.background = texture;
			scene.environment = texture;
			//add car
			const loader = new GLTFLoader().setPath( './assets/model/glb_model/' );
			loader.load( 'bmw_m8_competition_widebody.glb', async function ( gltf ) {

				const model = gltf.scene;

				// model.position.x = -5
				model.position.y = -20;
				model.position.z = 18;
				model.scale.set( 10, 10, 10 );
				

				// wait until the model can be added to the scene without blocking due to shader compilation

				await renderer.compileAsync( model, camera, scene );

				scene.add( model );

				car1 = model;

			} );
		})

3.监听键盘事件并更新模型位置

参考文章中已经实现了加速减速,于是我又新增了赛车左转右转功能,并且增加一个计分效果

ini 复制代码
// 更新模型的位置
function updateModelPosition(type) {
	if(car1) {
		if(type==='L') {
			car1.position.x =car1.position.x-0.5;
			car1.rotation.y = car1.rotation.y + 0.008;
		} else if(type==='R') {
			car1.position.x =car1.position.x+0.5;
			car1.rotation.y = car1.rotation.y - 0.008;
		} else if(type===''){
			car1.position.x = car1.position.x;
		}
	}
}

let speed = 0.01;
let points_total = 1;
let car_type = '';
function animate() {
    requestAnimationFrame(animate);
	//监听键盘事件调整模型位置
	updateModelPosition(car_type);
	points_total += 1*speed*100;
	console.log(points_total);
    material.uniforms.iTime.value += speed;
    renderer.render(scene, camera);
}
animate();

let div = document.getElementById("speed");

div.innerHTML = `速度:${speed*3000 | 0}`

let point = document.getElementById("points");

point.innerHTML = `得分:${points_total | 0}`
window.addEventListener('keydown',(event)=>{
    switch (event.key) {
        case 'w':
          speed += 0.0005;
          break;
        case 's':
          speed -= 0.0005;
          break;
        case ' ':
          speed = 0 ;
          break;
		case 'a':
			//按下左键调整模型的位置
			car_type = 'L'; 
		  break;
		case 'd':
			//按下左键调整模型的位置
			car_type = 'R'; 
		break;
		default:
			car_type = ''
			break;
    }

    div.innerHTML = `速度:${speed*3000 | 0}`
	point.innerHTML = `得分:${points_total | 0}`
})
//监听键盘松开事件
window.addEventListener('keyup',(event)=>{
	car_type = ''
})
setInterval(()=> {
	point.innerHTML = `得分:${points_total | 0}`
},200)

4.完整代码

ini 复制代码
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';

let _b = 9/16;
let _w = 900;
let _h = 900*_b;
let car1;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(40, _w / _h, 1, 1000);

camera.position.z = 112;


const vertexShader = `
        varying vec2 vUv;
        void main(){
        vUv = uv;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
    `;

let fragmentShader = `
    uniform float iTime;
    varying vec2 vUv;
    
    void main() {
        vec2 p = vUv;
        vec3 q=vec3(1.0,1.0,1.0),d=vec3(p-.5*q.xy,q.y)/q.y,c=vec3(0,.5,.7);
        q=d/(.1-d.y);
        float a=iTime, k=sin(.2*a), w = q.x *= q.x-=.05*k*k*k*q.z*q.z;
        vec3 col = vec3(0);
        col.xyz=d.y>.04?c:
            sin(4.*q.z+40.*a)>0.?
            w>2.?c.xyx:w>1.2?d.zzz:c.yyy:
            w>2.?c.xzx:w>1.2?c.yxx*2.:(w>.004?c:d).zzz;
        gl_FragColor = vec4(col,1.0);
    }
    `;

let width = 160;
const geometry = new THREE.PlaneGeometry(width,width*_b)

const material = new THREE.ShaderMaterial({
    uniforms: {
        iTime: { value: 0 },
    },
    fragmentShader: fragmentShader,
    vertexShader: vertexShader,
});

const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

const renderer = new THREE.WebGLRenderer();
renderer.setSize(_w,_h);

// new OrbitControls(camera, renderer.domElement);

renderer.setClearColor(0xFFFFFF, 1.0);
document.body.appendChild(renderer.domElement);


//添加环境光
// const directionalLight = new THREE.DirectionalLight( 0xffffff, 20 ); // 光的颜色和强度
// directionalLight.position.set( 0, 10, 0 ); // 光的方向
// scene.add( directionalLight );
new RGBELoader()
		.setPath( './assets/model/textures/equirectangular/' )
		.load( 'blouberg_sunrise_2_1k.hdr', function ( texture ) {
			texture.mapping = THREE.EquirectangularReflectionMapping;
			scene.background = texture;
			scene.environment = texture;
			//add car
			const loader = new GLTFLoader().setPath( './assets/model/glb_model/' );
			loader.load( 'bmw_m8_competition_widebody.glb', async function ( gltf ) {

				const model = gltf.scene;

				// model.position.x = -5
				model.position.y = -20;
				model.position.z = 18;
				model.scale.set( 10, 10, 10 );
				

				// wait until the model can be added to the scene without blocking due to shader compilation

				await renderer.compileAsync( model, camera, scene );

				scene.add( model );

				car1 = model;

			} );
		})
// 更新模型的位置
function updateModelPosition(type) {
	if(car1) {
		if(type==='L') {
			car1.position.x =car1.position.x-0.5;
			car1.rotation.y = car1.rotation.y + 0.008;
		} else if(type==='R') {
			car1.position.x =car1.position.x+0.5;
			car1.rotation.y = car1.rotation.y - 0.008;
		} else if(type===''){
			car1.position.x = car1.position.x;
		}
	}
}

let speed = 0.01;
let points_total = 1;
let car_type = '';
function animate() {
    requestAnimationFrame(animate);
	//监听键盘事件调整模型位置
	updateModelPosition(car_type);
	points_total += 1*speed*100;
	console.log(points_total);
    material.uniforms.iTime.value += speed;
    renderer.render(scene, camera);
}
animate();

let div = document.getElementById("speed");

div.innerHTML = `速度:${speed*3000 | 0}`

let point = document.getElementById("points");

point.innerHTML = `得分:${points_total | 0}`
window.addEventListener('keydown',(event)=>{
    switch (event.key) {
        case 'w':
          speed += 0.0005;
          break;
        case 's':
          speed -= 0.0005;
          break;
        case ' ':
          speed = 0 ;
          break;
		case 'a':
			//按下左键调整模型的位置
			car_type = 'L'; 
		  break;
		case 'd':
			//按下左键调整模型的位置
			car_type = 'R'; 
		break;
		default:
			car_type = ''
			break;
    }

    div.innerHTML = `速度:${speed*3000 | 0}`
	point.innerHTML = `得分:${points_total | 0}`
})
//监听键盘松开事件
window.addEventListener('keyup',(event)=>{
	car_type = ''
})
setInterval(()=> {
	point.innerHTML = `得分:${points_total | 0}`
},200)

5.总结

感谢参考文章中提供的基础与灵感,让我也实现了一把做游戏的感觉(虽然大部分都是crtl V),如果感兴趣的可以拉下源码,去增加一些更炫酷的功能以及游戏趣味性。

相关推荐
阿里巴啦2 小时前
用React+Three.js 做 3D Web版搭建三维交互场景:模型的可视化摆放与轻量交互
前端·react·three.js·模型可视化·web三维·web三维交互场景
阿里巴啦2 天前
React + Three.js + R3F + Vite 实战:可交互的三维粒子化展厅
react.js·three.js·粒子化·drei·postprocessing·三维粒子化
叫我詹躲躲2 天前
基于 Three.js 的 3D 地图可视化:核心原理与实现步骤
前端·three.js
map_3d_vis3 天前
JSAPIThree 加载单体三维模型学习笔记:SimpleModel 简易加载方式
学习笔记·three.js·gltf·glb·初学者·三维模型·mapvthree·jsapithree·simplemodel
Addisonx6 天前
深度复盘 III: 核心逻辑篇:构建 WebGL 数字孪生的“业务中枢”与“安全防线”
webgl·three.js
爱看书的小沐6 天前
【小沐学WebGIS】基于Three.JS绘制二三维地图地球晨昏效果(WebGL / vue / react )
javascript·vue.js·gis·webgl·three.js·opengl·晨昏线
Addisonx9 天前
深度复盘: WebGL 数字孪生前端架构:如何打造高颜值、高性能的 Web 3D 可视化系统
three.js
BUG创建者11 天前
thee.js完成线上展厅demo
开发语言·前端·javascript·css·html·css3·three.js
一千柯橘21 天前
从摄影新手到三维光影师:Three.js 核心要素的故事
前端·three.js
big男孩22 天前
OrbitControls 的完整原理
three.js