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

相关推荐
优雅永不过时·6 天前
three.js 通过着色器实现热力图效果
前端·javascript·智慧城市·three.js·热力图·着色器
Goboy11 天前
最佳ThreeJS实践 · 实现赛博朋克风格的三维图像气泡效果
前端·javascript·three.js
一嘴一个橘子22 天前
3.js - 漫天孔明灯(使用OrbitControls 锁定相机镜头)
three.js
一嘴一个橘子24 天前
3.js - 着色器设置点材质(螺旋星系特效)
three.js
Ian10251 个月前
Three.js new THREE.TextureLoader()纹理贴图使用png图片显示为黑色
前端·javascript·webgl·three.js·贴图·三维
Jedi Hongbin1 个月前
THREE.JS像素风格渲染
javascript·three.js·shader·后处理
unix2linux1 个月前
Parade Series - 3D Modeling
python·flask·html·jquery·webgl·three.js
zj靖2 个月前
three.js 几何体、材质和网格模型
three.js
一嘴一个橘子2 个月前
3.js - 顶点着色器、片元着色器的联系
three.js
Jedi Hongbin2 个月前
Threejs&WebGPU运动残影demo
javascript·three.js