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

相关推荐
全栈王校长3 小时前
Three.js 材质进阶
webgl·three.js
全栈王校长3 小时前
Three.js Geometry进阶
webgl·three.js
烛阴1 天前
3D字体TextGeometry
前端·webgl·three.js
全栈王校长1 天前
Three.js 开发快速入门
three.js
全栈王校长1 天前
Three.js 环境搭建与开发初识
three.js
DaMu1 天前
Dreamcore3D ARPG IDE “手搓”游戏引擎,轻量级实时3D创作工具,丝滑操作,即使小白也能轻松愉快的创作出属于你自己的游戏世界!
前端·架构·three.js
烛阴3 天前
从“无”到“有”:手动实现一个 3D 渲染循环全过程
前端·webgl·three.js
烛阴4 天前
拒绝配置地狱!5 分钟搭建 Three.js + Parcel 完美开发环境
前端·webgl·three.js
XiaoYu20027 天前
第9章 Three.js载入模型GLTF
前端·javascript·three.js
XiaoYu20028 天前
第8章 Three.js入门
前端·javascript·three.js