飞驰人生模拟器-基于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),如果感兴趣的可以拉下源码,去增加一些更炫酷的功能以及游戏趣味性。

相关推荐
Mintopia2 天前
Three.js 场景后处理的秘密:像素世界的魔法改造术
前端·javascript·three.js
Mintopia3 天前
Three.js 流水效果制作:从像素到波澜的魔法之旅
前端·javascript·three.js
不关小白白的事4 天前
🚗 Three.js 实战全解析:打造 SU7 展示 + 人物控制 + HDR 场景 + 碰撞检测
three.js
阿怼丶4 天前
🚶‍♂️基于 Three.js 的自定义角色漫游系统实战:支持碰撞检测与动画控制
前端·three.js
Mintopia4 天前
Three.js 3D 柱状图制作指南:从像素到立体的魔法之旅
前端·javascript·three.js
Mintopia5 天前
Three.js 顶点与颜色点的装配艺术:从像素到彩虹的底层之旅
前端·javascript·three.js
爱看书的小沐5 天前
【小沐杂货铺】基于Three.JS绘制汽车展示Car(WebGL、vue、react、autoshow、提供全部源代码)
汽车·vue3·react·webgl·three.js·opengl·autoshow
Mintopia6 天前
Three.js 中三角形到四边形的顶点变换:一场几何的华丽变身
前端·javascript·three.js
Mintopia7 天前
Three.js 中的噪声与图形变换:一场数字世界的舞蹈
前端·javascript·three.js
中国黄金Gold8 天前
Three.js OrbitControls:实现鼠标左键直接平移场景
three.js